11package io.github.sds100.keymapper.base.expertmode
22
3+ import android.content.ClipData
34import android.os.Build
45import android.provider.Settings
56import androidx.compose.animation.AnimatedVisibility
@@ -9,6 +10,7 @@ import androidx.compose.animation.fadeOut
910import androidx.compose.animation.shrinkVertically
1011import androidx.compose.foundation.BorderStroke
1112import androidx.compose.foundation.Image
13+ import androidx.compose.foundation.layout.Arrangement
1214import androidx.compose.foundation.layout.Box
1315import androidx.compose.foundation.layout.Column
1416import androidx.compose.foundation.layout.Row
@@ -30,6 +32,7 @@ import androidx.compose.material.icons.automirrored.rounded.HelpOutline
3032import androidx.compose.material.icons.rounded.Check
3133import androidx.compose.material.icons.rounded.Checklist
3234import androidx.compose.material.icons.rounded.Close
35+ import androidx.compose.material.icons.rounded.ContentCopy
3336import androidx.compose.material.icons.rounded.Notifications
3437import androidx.compose.material.icons.rounded.Numbers
3538import androidx.compose.material.icons.rounded.RestartAlt
@@ -53,12 +56,16 @@ import androidx.compose.material3.TextButton
5356import androidx.compose.material3.TopAppBar
5457import androidx.compose.runtime.Composable
5558import androidx.compose.runtime.getValue
59+ import androidx.compose.runtime.rememberCoroutineScope
5660import androidx.compose.ui.Alignment
5761import androidx.compose.ui.Modifier
5862import androidx.compose.ui.graphics.Color
63+ import androidx.compose.ui.platform.ClipEntry
64+ import androidx.compose.ui.platform.LocalClipboard
5965import androidx.compose.ui.platform.LocalContext
6066import androidx.compose.ui.platform.LocalLayoutDirection
6167import androidx.compose.ui.res.stringResource
68+ import androidx.compose.ui.text.font.FontFamily
6269import androidx.compose.ui.text.style.TextAlign
6370import androidx.compose.ui.tooling.preview.Preview
6471import androidx.compose.ui.unit.dp
@@ -73,6 +80,7 @@ import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcon
7380import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcons
7481import io.github.sds100.keymapper.common.utils.SettingsUtils
7582import io.github.sds100.keymapper.common.utils.State
83+ import kotlinx.coroutines.launch
7684
7785@Composable
7886fun ExpertModeScreen (modifier : Modifier = Modifier , viewModel : ExpertModeViewModel ) {
@@ -98,6 +106,7 @@ fun ExpertModeScreen(modifier: Modifier = Modifier, viewModel: ExpertModeViewMod
98106 onRequestNotificationPermissionClick = viewModel::onRequestNotificationPermissionClick,
99107 onAutoStartAtBootToggled = { viewModel.onAutoStartBootToggled() },
100108 onLaunchDeveloperOptionsClick = viewModel::onLaunchDeveloperOptionsClick,
109+ onGetShellStartCommandClick = viewModel::onGetShellStartCommandClick,
101110 )
102111 }
103112}
@@ -179,6 +188,7 @@ private fun Content(
179188 onRequestNotificationPermissionClick : () -> Unit = {},
180189 onAutoStartAtBootToggled : () -> Unit = {},
181190 onLaunchDeveloperOptionsClick : () -> Unit = {},
191+ onGetShellStartCommandClick : () -> Unit = {},
182192) {
183193 Column (modifier = modifier.verticalScroll(rememberScrollState())) {
184194 AnimatedVisibility (
@@ -227,6 +237,7 @@ private fun Content(
227237 onRequestNotificationPermissionClick = onRequestNotificationPermissionClick,
228238 onAutoStartAtBootToggled = onAutoStartAtBootToggled,
229239 onLaunchDeveloperOptionsClick = onLaunchDeveloperOptionsClick,
240+ onGetShellStartCommandClick = onGetShellStartCommandClick,
230241 )
231242 }
232243 }
@@ -251,6 +262,7 @@ private fun LoadedContent(
251262 onRequestNotificationPermissionClick : () -> Unit = {},
252263 onAutoStartAtBootToggled : () -> Unit = {},
253264 onLaunchDeveloperOptionsClick : () -> Unit = {},
265+ onGetShellStartCommandClick : () -> Unit = {},
254266) {
255267 Column (modifier) {
256268 OptionsHeaderRow (
@@ -345,7 +357,9 @@ private fun LoadedContent(
345357 isChecked = state.autoStartBootChecked,
346358 onCheckedChange = { onAutoStartAtBootToggled() },
347359 isEnabled = state.autoStartBootEnabled,
360+
348361 )
362+ Spacer (modifier = Modifier .height(8 .dp))
349363 }
350364
351365 is ExpertModeState .Stopped -> {
@@ -449,12 +463,22 @@ private fun LoadedContent(
449463 buttonText = setupKeyMapperText,
450464 onButtonClick = onSetupWithKeyMapperClick,
451465 enabled =
452- Build .VERSION .SDK_INT >= Build .VERSION_CODES .R &&
453- state.isNotificationPermissionGranted,
466+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .R &&
467+ state.isNotificationPermissionGranted,
454468 isLoading = state.isStarting,
455469 )
456470
457471 Spacer (modifier = Modifier .height(8 .dp))
472+
473+ ShellStartCard (
474+ modifier = Modifier
475+ .fillMaxWidth()
476+ .padding(horizontal = 8 .dp),
477+ shellStartCommandState = state.shellStartCommandState,
478+ onGetShellStartCommandClick = onGetShellStartCommandClick,
479+ )
480+
481+ Spacer (modifier = Modifier .height(8 .dp))
458482 }
459483 }
460484 }
@@ -718,6 +742,141 @@ private fun SetupCard(
718742 }
719743}
720744
745+ @Composable
746+ private fun ShellStartCard (
747+ modifier : Modifier = Modifier ,
748+ shellStartCommandState : ShellStartCommandState ,
749+ onGetShellStartCommandClick : () -> Unit ,
750+ ) {
751+ val clipboard = LocalClipboard .current
752+ val scope = rememberCoroutineScope()
753+
754+ OutlinedCard (modifier = modifier) {
755+ Spacer (modifier = Modifier .height(16 .dp))
756+ Row (modifier = Modifier .padding(horizontal = 16 .dp)) {
757+ Icon (
758+ imageVector = Icons .Rounded .Usb ,
759+ contentDescription = null ,
760+ tint = MaterialTheme .colorScheme.onSurface,
761+ )
762+
763+ Spacer (modifier = Modifier .width(8 .dp))
764+
765+ Text (
766+ text = stringResource(R .string.expert_mode_shell_start_title),
767+ style = MaterialTheme .typography.titleMedium,
768+ )
769+ }
770+
771+ Spacer (modifier = Modifier .height(8 .dp))
772+
773+ Text (
774+ modifier = Modifier .padding(horizontal = 16 .dp),
775+ text = stringResource(R .string.expert_mode_shell_start_description),
776+ style = MaterialTheme .typography.bodyMedium,
777+ )
778+
779+ Spacer (modifier = Modifier .height(8 .dp))
780+
781+ when (shellStartCommandState) {
782+ is ShellStartCommandState .Idle -> {
783+ FilledTonalButton (
784+ modifier = Modifier
785+ .align(Alignment .End )
786+ .padding(horizontal = 16 .dp),
787+ onClick = onGetShellStartCommandClick,
788+ ) {
789+ Text (stringResource(R .string.expert_mode_shell_start_get_command))
790+ }
791+ }
792+
793+ is ShellStartCommandState .Loading -> {
794+ FilledTonalButton (
795+ modifier = Modifier
796+ .align(Alignment .End )
797+ .padding(horizontal = 16 .dp),
798+ onClick = {},
799+ enabled = false ,
800+ ) {
801+ CircularProgressIndicator (
802+ modifier = Modifier .size(18 .dp),
803+ strokeWidth = 2 .dp,
804+ color = LocalContentColor .current,
805+ )
806+ Spacer (modifier = Modifier .width(8 .dp))
807+ Text (stringResource(R .string.expert_mode_shell_start_get_command))
808+ }
809+ }
810+
811+ is ShellStartCommandState .Loaded -> {
812+ Row (
813+ modifier = Modifier .fillMaxWidth().padding(horizontal = 16 .dp),
814+ horizontalArrangement = Arrangement .End ,
815+ ) {
816+ Text (
817+ modifier = Modifier .weight(1f ),
818+ text = shellStartCommandState.command,
819+ style = MaterialTheme .typography.bodyMedium.copy(
820+ fontFamily = FontFamily .Monospace ,
821+ ),
822+ )
823+
824+ Spacer (modifier = Modifier .height(8 .dp))
825+
826+ val clipEntry = ClipEntry (
827+ ClipData .newPlainText(
828+ stringResource(
829+ R .string.expert_mode_shell_start_clipboard_label,
830+ ),
831+ shellStartCommandState.command,
832+ ),
833+ )
834+
835+ IconButton (
836+ onClick = {
837+ scope.launch {
838+ clipboard.setClipEntry(clipEntry)
839+ }
840+ },
841+ ) {
842+ Icon (
843+ imageVector = Icons .Rounded .ContentCopy ,
844+ contentDescription = stringResource(
845+ R .string.expert_mode_shell_start_copy_content_description,
846+ ),
847+ )
848+ }
849+ }
850+ }
851+
852+ is ShellStartCommandState .Error -> {
853+ Column (
854+ modifier = Modifier
855+ .fillMaxWidth()
856+ .padding(horizontal = 16 .dp),
857+ ) {
858+ Text (
859+ text = stringResource(R .string.expert_mode_shell_start_error),
860+ style = MaterialTheme .typography.bodyMedium,
861+ color = MaterialTheme .colorScheme.error,
862+ )
863+
864+ Spacer (modifier = Modifier .height(8 .dp))
865+
866+ FilledTonalButton (
867+ modifier = Modifier .align(Alignment .End ),
868+ onClick = onGetShellStartCommandClick,
869+ ) {
870+ Text (stringResource(R .string.expert_mode_shell_start_retry))
871+ }
872+ }
873+ }
874+ }
875+
876+ Spacer (modifier = Modifier .height(16 .dp))
877+ }
878+ }
879+
721880@Composable
722881private fun ExpertModeInfoCard (modifier : Modifier = Modifier , onDismiss : () -> Unit = {}) {
723882 OutlinedCard (
@@ -787,6 +946,7 @@ private fun Preview() {
787946 shizukuSetupState = ShizukuSetupState .PERMISSION_GRANTED ,
788947 isNotificationPermissionGranted = true ,
789948 isStarting = false ,
949+ shellStartCommandState = ShellStartCommandState .Idle ,
790950 ),
791951 ),
792952 showInfoCard = true ,
@@ -878,6 +1038,7 @@ private fun PreviewNotificationPermissionNotGranted() {
8781038 shizukuSetupState = ShizukuSetupState .PERMISSION_GRANTED ,
8791039 isNotificationPermissionGranted = false ,
8801040 isStarting = false ,
1041+ shellStartCommandState = ShellStartCommandState .Idle ,
8811042 ),
8821043 ),
8831044 showInfoCard = false ,
@@ -912,3 +1073,30 @@ private fun PreviewUsbDebuggingSecuritySettingsCard() {
9121073 }
9131074 }
9141075}
1076+
1077+ @Preview
1078+ @Composable
1079+ private fun PreviewShellStartCard () {
1080+ KeyMapperTheme {
1081+ ExpertModeScreen {
1082+ Content (
1083+ warningState = ExpertModeWarningState .Understood ,
1084+ setupState = State .Data (
1085+ ExpertModeState .Stopped (
1086+ isRootGranted = false ,
1087+ shizukuSetupState = ShizukuSetupState .NOT_FOUND ,
1088+ isNotificationPermissionGranted = true ,
1089+ isStarting = false ,
1090+ shellStartCommandState = ShellStartCommandState .Loaded (
1091+ " sh /storage/emulated/0/Android/data/io.github.sds100.keymapper/files/start.sh" ,
1092+ ),
1093+ ),
1094+ ),
1095+ showInfoCard = false ,
1096+ onInfoCardDismiss = {},
1097+ onAutoStartAtBootToggled = {},
1098+ onLaunchDeveloperOptionsClick = {},
1099+ )
1100+ }
1101+ }
1102+ }
0 commit comments