Skip to content

Commit 75952ab

Browse files
committed
#1964 feat: show the command to start Expert Mode with a shell command
1 parent b71dba8 commit 75952ab

File tree

10 files changed

+342
-123
lines changed

10 files changed

+342
-123
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
#### TO BE RELEASED
44

5+
## Added
6+
7+
- #1964 show the command to start Expert Mode with a shell command.
8+
59
## Bug fixes
610

711
- #1968 Device controls action no longer works on Android 16+ so it has been disabled on new Android versions.

app/version.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
VERSION_NAME=4.0.0-beta.06
2-
VERSION_CODE=223
2+
VERSION_CODE=224

base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeScreen.kt

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.sds100.keymapper.base.expertmode
22

3+
import android.content.ClipData
34
import android.os.Build
45
import android.provider.Settings
56
import androidx.compose.animation.AnimatedVisibility
@@ -9,6 +10,7 @@ import androidx.compose.animation.fadeOut
910
import androidx.compose.animation.shrinkVertically
1011
import androidx.compose.foundation.BorderStroke
1112
import androidx.compose.foundation.Image
13+
import androidx.compose.foundation.layout.Arrangement
1214
import androidx.compose.foundation.layout.Box
1315
import androidx.compose.foundation.layout.Column
1416
import androidx.compose.foundation.layout.Row
@@ -30,6 +32,7 @@ import androidx.compose.material.icons.automirrored.rounded.HelpOutline
3032
import androidx.compose.material.icons.rounded.Check
3133
import androidx.compose.material.icons.rounded.Checklist
3234
import androidx.compose.material.icons.rounded.Close
35+
import androidx.compose.material.icons.rounded.ContentCopy
3336
import androidx.compose.material.icons.rounded.Notifications
3437
import androidx.compose.material.icons.rounded.Numbers
3538
import androidx.compose.material.icons.rounded.RestartAlt
@@ -53,12 +56,16 @@ import androidx.compose.material3.TextButton
5356
import androidx.compose.material3.TopAppBar
5457
import androidx.compose.runtime.Composable
5558
import androidx.compose.runtime.getValue
59+
import androidx.compose.runtime.rememberCoroutineScope
5660
import androidx.compose.ui.Alignment
5761
import androidx.compose.ui.Modifier
5862
import androidx.compose.ui.graphics.Color
63+
import androidx.compose.ui.platform.ClipEntry
64+
import androidx.compose.ui.platform.LocalClipboard
5965
import androidx.compose.ui.platform.LocalContext
6066
import androidx.compose.ui.platform.LocalLayoutDirection
6167
import androidx.compose.ui.res.stringResource
68+
import androidx.compose.ui.text.font.FontFamily
6269
import androidx.compose.ui.text.style.TextAlign
6370
import androidx.compose.ui.tooling.preview.Preview
6471
import androidx.compose.ui.unit.dp
@@ -73,6 +80,7 @@ import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcon
7380
import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcons
7481
import io.github.sds100.keymapper.common.utils.SettingsUtils
7582
import io.github.sds100.keymapper.common.utils.State
83+
import kotlinx.coroutines.launch
7684

7785
@Composable
7886
fun 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
722881
private 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

Comments
 (0)