diff --git a/build.gradle.kts b/build.gradle.kts index 1466bdd..ce462f2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ buildscript { google() mavenCentral() } - val agpVersion by extra("7.1.1") + val agpVersion by extra("7.1.2") dependencies { classpath("com.android.tools.build:gradle:$agpVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index 8cecf82..4b96c8d 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -11,8 +11,6 @@ val verName: String by rootProject.extra val androidSourceCompatibility: JavaVersion by rootProject.extra val androidTargetCompatibility: JavaVersion by rootProject.extra -val composeVersion = "1.2.0-alpha03" - plugins { id("com.android.application") id("kotlin-parcelize") @@ -52,7 +50,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = composeVersion + kotlinCompilerExtensionVersion = "1.2.0-alpha03" } sourceSets["main"].assets.srcDirs(rootProject.projectDir.resolve("out/assets")) @@ -85,14 +83,14 @@ dependencies { implementation(projects.patch) implementation("androidx.core:core-ktx:1.7.0") - implementation("androidx.activity:activity-compose:1.5.0-alpha02") - implementation("androidx.compose.material:material-icons-extended:1.1.0") - implementation("androidx.compose.material3:material3:1.0.0-alpha05") - implementation("androidx.compose.runtime:runtime-livedata:$composeVersion") - implementation("androidx.compose.ui:ui:$composeVersion") - implementation("androidx.compose.ui:ui-tooling:$composeVersion") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha02") - implementation("androidx.navigation:navigation-compose:2.5.0-alpha02") + implementation("androidx.activity:activity-compose:1.5.0-alpha03") + implementation("androidx.compose.material:material-icons-extended:1.1.1") + implementation("androidx.compose.material3:material3:1.0.0-alpha06") + implementation("androidx.compose.runtime:runtime-livedata:1.1.1") + implementation("androidx.compose.ui:ui:1.1.1") + implementation("androidx.compose.ui:ui-tooling:1.1.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha03") + implementation("androidx.navigation:navigation-compose:2.5.0-alpha03") implementation("androidx.preference:preference:1.2.0") implementation("com.google.accompanist:accompanist-drawablepainter:0.24.2-alpha") implementation("com.google.accompanist:accompanist-navigation-animation:0.24.2-alpha") diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt index 46af9ad..7f3e273 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -34,23 +33,13 @@ class MainActivity : ComponentActivity() { LSPTheme { CompositionLocalProvider(LocalNavController provides navController) { Scaffold( - topBar = { - navController.currentBackStackEntry?.let { - CompositionLocalProvider(LocalViewModelStoreOwner provides it) { - currentPage?.topBar?.invoke() - } - } - }, bottomBar = { MainNavigationBar(mainPage) { mainPage = it - navController.navigate(it.name) - } - }, - floatingActionButton = { - navController.currentBackStackEntry?.let { - CompositionLocalProvider(LocalViewModelStoreOwner provides it) { - currentPage?.fab?.invoke() + navController.navigate(it.name) { + currentRoute?.let { route -> + popUpTo(route) { inclusive = true } + } } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt index 29fb646..4a08b3a 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt @@ -1,18 +1,25 @@ package org.lsposed.lspatch.ui.page +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import org.lsposed.lspatch.R +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomePage() { + Scaffold( + topBar = { TopBar() } + ) { innerPadding -> + } } @Composable -fun HomeTopBar() { +private fun TopBar() { SmallTopAppBar( title = { Text(stringResource(R.string.app_name)) } ) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt index d49b45d..a90241f 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt @@ -2,31 +2,36 @@ package org.lsposed.lspatch.ui.page import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.SmallTopAppBar -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import org.lsposed.lspatch.R import org.lsposed.lspatch.ui.util.LocalNavController +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ManageTopBar() { +fun ManagePage() { + val navController = LocalNavController.current + Scaffold( + topBar = { TopBar() }, + floatingActionButton = { + Fab { navController.navigate(PageList.NewPatch.name) } + } + ) { innerPadding -> + + } +} + +@Composable +private fun TopBar() { SmallTopAppBar( title = { Text(PageList.Manage.title) } ) } @Composable -fun ManageFab() { - val navController = LocalNavController.current - FloatingActionButton(onClick = { navController.navigate(PageList.NewPatch.name) }) { +private fun Fab(onClick: () -> Unit) { + FloatingActionButton(onClick = onClick) { Icon(Icons.Filled.Add, stringResource(R.string.add)) } } - -@Composable -fun ManagePage() { - -} diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt index ebdb44e..053c317 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt @@ -1,5 +1,8 @@ package org.lsposed.lspatch.ui.page +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.os.Build import android.util.Log import androidx.compose.animation.animateContentSize @@ -29,8 +32,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavBackStackEntry import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.rememberPermissionState @@ -49,34 +51,27 @@ import org.lsposed.lspatch.ui.viewmodel.AppInfo import org.lsposed.patch.util.Logger import java.io.File -enum class PatchState { +private enum class PatchState { SELECTING, CONFIGURING, SUBMITTING, PATCHING, FINISHED, ERROR } -class NewPatchPageViewModel : ViewModel() { - var patchState by mutableStateOf(PatchState.SELECTING) - var patchOptions by mutableStateOf(null) -} - +@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class) @Composable -fun NewPatchFab() { - val viewModel = viewModel() - if (viewModel.patchState == PatchState.CONFIGURING) { - ExtendedFloatingActionButton( - text = { Text(stringResource(R.string.patch_start)) }, - icon = { Icon(Icons.Outlined.AutoFixHigh, null) }, - onClick = { viewModel.patchState = PatchState.SUBMITTING } - ) - } -} - -@OptIn(ExperimentalPermissionsApi::class) -@Composable -fun NewPatchPage() { - val viewModel = viewModel() +fun NewPatchPage(entry: NavBackStackEntry) { val navController = LocalNavController.current - val patchApp by navController.currentBackStackEntry!!.observeState("appInfo") - if (viewModel.patchState == PatchState.SELECTING && patchApp != null) viewModel.patchState = PatchState.CONFIGURING + val patchApp by entry.observeState("appInfo") + val isCancelled by entry.observeState("isCancelled") + var patchState by rememberSaveable { mutableStateOf(PatchState.SELECTING) } + var patchOptions by rememberSaveable { mutableStateOf(null) } + if (patchState == PatchState.SELECTING) { + when { + isCancelled == true -> { + LaunchedEffect(entry) { navController.popBackStack() } + return + } + patchApp != null -> patchState = PatchState.CONFIGURING + } + } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { val filePermissionState = rememberPermissionState(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) @@ -100,17 +95,79 @@ fun NewPatchPage() { } } - Log.d(TAG, "NewPatchPage: ${viewModel.patchState}") - when (viewModel.patchState) { - PatchState.SELECTING -> navController.navigate(PageList.SelectApps.name + "/false") - PatchState.CONFIGURING, PatchState.SUBMITTING -> PatchOptionsPage(patchApp!!) - PatchState.PATCHING, PatchState.FINISHED, PatchState.ERROR -> DoPatchPage(viewModel.patchOptions!!) + Log.d(TAG, "NewPatchPage: $patchState") + if (patchState == PatchState.SELECTING) { + LaunchedEffect(entry) { + navController.navigate(PageList.SelectApps.name + "/false") + } + } else { + Scaffold( + topBar = { TopBar(patchApp!!) }, + floatingActionButton = { + if (patchState == PatchState.CONFIGURING) { + ConfiguringFab { patchState = PatchState.SUBMITTING } + } + } + ) { innerPadding -> + if (patchState == PatchState.CONFIGURING || patchState == PatchState.SUBMITTING) { + PatchOptionsBody( + modifier = Modifier.padding(innerPadding), + patchState = patchState, + patchApp = patchApp!!, + onSubmit = { + patchOptions = it + patchState = PatchState.PATCHING + } + ) + } else { + DoPatchBody( + modifier = Modifier.padding(innerPadding), + patchState = patchState, + patchOptions = patchOptions!!, + onFinish = { patchState = PatchState.FINISHED }, + onFail = { patchState = PatchState.ERROR } + ) + } + } } } @Composable -private fun PatchOptionsPage(patchApp: AppInfo) { - val viewModel = viewModel() +private fun TopBar(patchApp: AppInfo) { + SmallTopAppBar( + title = { + Column { + Text( + text = patchApp.label, + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(horizontal = 24.dp) + ) + Text( + text = patchApp.app.packageName, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(horizontal = 24.dp) + ) + } + } + ) +} + +@Composable +private fun ConfiguringFab(onClick: () -> Unit) { + ExtendedFloatingActionButton( + text = { Text(stringResource(R.string.patch_start)) }, + icon = { Icon(Icons.Outlined.AutoFixHigh, null) }, + onClick = onClick + ) +} + +@Composable +private fun PatchOptionsBody( + modifier: Modifier, + patchState: PatchState, + patchApp: AppInfo, + onSubmit: (Patcher.Options) -> Unit +) { val navController = LocalNavController.current var useManager by rememberSaveable { mutableStateOf(true) } var debuggable by rememberSaveable { mutableStateOf(false) } @@ -122,9 +179,9 @@ private fun PatchOptionsPage(patchApp: AppInfo) { val embeddedModules = navController.currentBackStackEntry!! .savedStateHandle.getLiveData>("selected", SnapshotStateList()) - if (viewModel.patchState == PatchState.SUBMITTING) LaunchedEffect(patchApp) { + if (patchState == PatchState.SUBMITTING) LaunchedEffect(patchApp) { if (useManager) embeddedModules.value?.clear() - viewModel.patchOptions = Patcher.Options( + val options = Patcher.Options( apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk debuggable = debuggable, sigbypassLevel = sigBypassLevel, @@ -134,24 +191,10 @@ private fun PatchOptionsPage(patchApp: AppInfo) { verbose = true, embeddedModules = embeddedModules.value?.map { it.app.sourceDir } ?: emptyList() // TODO: Split Apk ) - viewModel.patchState = PatchState.PATCHING + onSubmit(options) } - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(top = 24.dp) - ) { - Text( - text = patchApp.label, - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.padding(horizontal = 24.dp) - ) - Text( - text = patchApp.app.packageName, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(horizontal = 24.dp) - ) + Column(modifier.verticalScroll(rememberScrollState())) { Text( text = stringResource(R.string.patch_mode), style = MaterialTheme.typography.titleLarge, @@ -219,9 +262,14 @@ private fun PatchOptionsPage(patchApp: AppInfo) { } @Composable -private fun DoPatchPage(patcherOptions: Patcher.Options) { +private fun DoPatchBody( + modifier: Modifier, + patchState: PatchState, + patchOptions: Patcher.Options, + onFinish: () -> Unit, + onFail: () -> Unit +) { val context = LocalContext.current - val viewModel = viewModel() val navController = LocalNavController.current val logs = remember { mutableStateListOf>() } val logger = remember { @@ -245,27 +293,27 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { } } - LaunchedEffect(patcherOptions) { + LaunchedEffect(patchOptions) { try { - Patcher.patch(context, logger, patcherOptions) - viewModel.patchState = PatchState.FINISHED + Patcher.patch(context, logger, patchOptions) + onFinish() } catch (t: Throwable) { logger.e(t.message.orEmpty()) logger.e(t.stackTraceToString()) - viewModel.patchState = PatchState.ERROR + onFail() } finally { - File(patcherOptions.outputPath).deleteRecursively() + File(patchOptions.outputPath).deleteRecursively() } } Column( - Modifier + modifier .fillMaxSize() .padding(24.dp) .wrapContentHeight() .animateContentSize(spring(stiffness = Spring.StiffnessLow)) ) { - ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) { + ShimmerAnimation(enabled = patchState == PatchState.PATCHING) { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace) ) { @@ -296,7 +344,7 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { } } - if (viewModel.patchState == PatchState.FINISHED) { + if (patchState == PatchState.FINISHED) { Row(Modifier.padding(top = 12.dp)) { Button( onClick = { navController.popBackStack() }, @@ -310,6 +358,23 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { content = { Text(stringResource(R.string.patch_install)) } ) } + } else if (patchState == PatchState.ERROR) { + Row(Modifier.padding(top = 12.dp)) { + Button( + onClick = { navController.popBackStack() }, + modifier = Modifier.weight(1f), + content = { Text(stringResource(R.string.patch_return)) } + ) + Spacer(Modifier.weight(0.2f)) + Button( + onClick = { + val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText("LSPatch", logs.joinToString { it.second + "\n" })) + }, + modifier = Modifier.weight(1f), + content = { Text(stringResource(R.string.patch_copy_error)) } + ) + } } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt index ea48a7a..86660a9 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt @@ -16,46 +16,35 @@ enum class PageList( val iconSelected: ImageVector? = null, val iconNotSelected: ImageVector? = null, val arguments: List = emptyList(), - val topBar: @Composable () -> Unit, - val fab: @Composable () -> Unit = {}, val body: @Composable NavBackStackEntry.() -> Unit ) { Home( iconSelected = Icons.Filled.Home, iconNotSelected = Icons.Outlined.Home, - topBar = { HomeTopBar() }, body = { HomePage() } ), Manage( iconSelected = Icons.Filled.Dashboard, iconNotSelected = Icons.Outlined.Dashboard, - topBar = { ManageTopBar() }, - fab = { ManageFab() }, - body = {} + body = { ManagePage() } ), Repo( iconSelected = Icons.Filled.GetApp, iconNotSelected = Icons.Outlined.GetApp, - topBar = {}, body = {} ), Settings( iconSelected = Icons.Filled.Settings, iconNotSelected = Icons.Outlined.Settings, - topBar = {}, body = {} ), NewPatch( - topBar = {}, - fab = { NewPatchFab() }, - body = { NewPatchPage() } + body = { NewPatchPage(this) } ), SelectApps( arguments = listOf( navArgument("multiSelect") { type = NavType.BoolType } ), - topBar = { SelectAppsTopBar() }, - fab = { SelectAppsFab() }, body = { SelectAppsPage(this) } ); diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt index 53a3530..b4914bd 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt @@ -1,15 +1,14 @@ package org.lsposed.lspatch.ui.page import android.content.pm.ApplicationInfo +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Done -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.SmallTopAppBar -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -29,40 +28,53 @@ import org.lsposed.lspatch.ui.util.setState import org.lsposed.lspatch.ui.viewmodel.AppInfo import org.lsposed.lspatch.ui.viewmodel.SelectAppsViewModel +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun SelectAppsTopBar() { +fun SelectAppsPage(entry: NavBackStackEntry) { + val navController = LocalNavController.current + val multiSelect = entry.arguments?.get("multiSelect") as? Boolean + ?: throw IllegalArgumentException("multiSelect is null") + + BackHandler { + navController.previousBackStackEntry!!.setState("isCancelled", true) + navController.popBackStack() + } + + Scaffold( + topBar = { TopBar() }, + floatingActionButton = { + if (multiSelect) MultiSelectFab() + } + ) { innerPadding -> + if (multiSelect) { + MultiSelect( + modifier = Modifier.padding(innerPadding), + filter = { it.app.metaData?.get("xposedminversion") != null } + ) + } else { + SingleSelect(modifier = Modifier.padding(innerPadding)) + } + } +} + +@Composable +private fun TopBar() { SmallTopAppBar( title = { Text(stringResource(R.string.page_select_apps)) } ) } @Composable -fun SelectAppsFab() { +private fun MultiSelectFab() { val navController = LocalNavController.current - val viewModel = viewModel() - val multiSelect = navController.currentBackStackEntry?.arguments?.get("multiSelect") as? Boolean - ?: throw IllegalArgumentException("multiSelect is null") - - if (multiSelect) { - FloatingActionButton(onClick = { viewModel.done = true }) { - Icon(Icons.Outlined.Done, stringResource(R.string.add)) - } - } -} - -@Composable -fun SelectAppsPage(entry: NavBackStackEntry) { - val multiSelect = entry.arguments?.get("multiSelect") as? Boolean - ?: throw IllegalArgumentException("multiSelect is null") - if (multiSelect) { - MultiSelect(filter = { it.app.metaData?.get("xposedminversion") != null }) - } else { - SingleSelect() + FloatingActionButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Outlined.Done, stringResource(R.string.add)) } } @Composable private fun SingleSelect( + modifier: Modifier, filter: (AppInfo) -> Boolean = { it.app.flags and ApplicationInfo.FLAG_SYSTEM == 0 } ) { val context = LocalContext.current @@ -75,7 +87,7 @@ private fun SingleSelect( SwipeRefresh( state = rememberSwipeRefreshState(viewModel.isRefreshing), onRefresh = { viewModel.filterAppList(context, true, filter) }, - modifier = Modifier.fillMaxSize() + modifier = modifier.fillMaxSize() ) { LazyColumn { items(viewModel.filteredList) { @@ -95,6 +107,7 @@ private fun SingleSelect( @Composable private fun MultiSelect( + modifier: Modifier, filter: (AppInfo) -> Boolean = { true } ) { val context = LocalContext.current @@ -105,16 +118,11 @@ private fun MultiSelect( LaunchedEffect(viewModel) { viewModel.filterAppList(context, false, filter) } - if (viewModel.done) { - LaunchedEffect(viewModel) { - navController.popBackStack() - } - } SwipeRefresh( state = rememberSwipeRefreshState(viewModel.isRefreshing), onRefresh = { viewModel.filterAppList(context, true, filter) }, - modifier = Modifier.fillMaxSize() + modifier = modifier.fillMaxSize() ) { LazyColumn { items(viewModel.filteredList) { diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt index fa32bb3..7e24869 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt @@ -29,8 +29,6 @@ class SelectAppsViewModel : ViewModel() { Log.d(TAG, "SelectAppsViewModel ${toString().substringAfterLast('@')} construct") } - var done by mutableStateOf(false) - var isRefreshing by mutableStateOf(false) private set diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index e72c798..a4afbd6 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Start Patch Return Install + Copy error Select Apps