Sure Dixit! Tamne ek full working example apo chhu je tamara Android Compose project ma terminal jevu command interface aapse. Aa example ma basic Linux style commands support chhe: pwd, ls, cd, cat, help, echo, date, mkdir, rm.
Current directory state is tracked with currentDir.
- - Commands pwd, ls, cd, cat, echo, date, mkdir, rm, and help are implemented.
- - Terminal UI shows history of commands + output.
- - User can type commands at bottom input field.
- - cd changes directory in app state; ls lists files relative to current directory.
Terminal UI with scrolling
Input TextField to send commands (optional)
Proper keyboard handling with the fixes you asked for
Run shell commands and handle errors gracefully
package com.example.testtinge_andorid
import android.os.Bundle
import android.os.Environment
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TerminalScreen()
}
}
}
@Composable
fun TerminalScreen() {
var currentDir by remember { mutableStateOf(Environment.getExternalStorageDirectory().path) }
val messages = remember { mutableStateListOf<String>() }
LaunchedEffect(Unit) {
messages.add("Device Activated Successfully // shell ready")
messages.add("Type 'help' to see commands.")
}
// Function to run commands
fun runShellCommand(command: String): String {
val trimmedCmd = command.trim()
if(trimmedCmd.isEmpty()) return ""
val args = trimmedCmd.split(" ")
return when (args[0]) {
"pwd" -> currentDir
"ls" -> {
val path = if (args.size > 1) {
if (args[1].startsWith("/")) args[1] else "$currentDir/${args[1]}"
} else currentDir
val dir = File(path)
if (dir.exists() && dir.isDirectory) {
val files = dir.listFiles()
files?.joinToString(" ") { it.name } ?: "Empty directory"
} else {
"Directory not found: $path"
}
}
"cd" -> {
if (args.size < 2) return "Usage: cd <directory>"
val newPath = if (args[1].startsWith("/")) args[1] else "$currentDir/${args[1]}"
val dir = File(newPath).canonicalFile
if (dir.exists() && dir.isDirectory) {
currentDir = dir.path
"Changed directory to: $currentDir"
} else {
"Directory not found: $newPath"
}
}
"cat" -> {
if (args.size < 2) return "Usage: cat <filename>"
val filePath = if (args[1].startsWith("/")) args[1] else "$currentDir/${args[1]}"
val file = File(filePath)
if (file.exists() && file.isFile) {
file.readText()
} else {
"File not found: $filePath"
}
}
"echo" -> {
args.drop(1).joinToString(" ")
}
"date" -> {
val sdf = SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.getDefault())
sdf.format(Date())
}
"mkdir" -> {
if (args.size < 2) return "Usage: mkdir <directory>"
val newDirPath = if (args[1].startsWith("/")) args[1] else "$currentDir/${args[1]}"
val dir = File(newDirPath)
if (dir.exists()) {
"Directory already exists: $newDirPath"
} else {
if (dir.mkdirs()) "Directory created: $newDirPath"
else "Failed to create directory: $newDirPath"
}
}
"rm" -> {
if (args.size < 2) return "Usage: rm <filename|directory>"
val targetPath = if (args[1].startsWith("/")) args[1] else "$currentDir/${args[1]}"
val target = File(targetPath)
if (!target.exists()) {
"File or directory not found: $targetPath"
} else {
if (target.isDirectory) {
if (target.deleteRecursively()) "Directory deleted: $targetPath"
else "Failed to delete directory: $targetPath"
} else {
if (target.delete()) "File deleted: $targetPath"
else "Failed to delete file: $targetPath"
}
}
}
"help" -> {
"""
Available commands:
pwd - Print current directory
ls [dir] - List files in directory
cd <dir> - Change directory
cat <file> - Display file contents
echo <text> - Display text
date - Show current date and time
mkdir <dir> - Create directory
rm <file|dir> - Delete file or directory
help - Show this help message
""".trimIndent()
}
else -> "Unknown command: ${args[0]}"
}
}
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.padding(16.dp)
) {
Terminal(
messages = messages,
onCommandEntered = { command ->
messages.add("└─\$ $command")
val output = runShellCommand(command)
messages.add(output)
}
)
}
}
@Composable
fun Terminal(
messages: List<String>,
modifier: Modifier = Modifier,
onCommandEntered: (String) -> Unit
) {
val scrollState = rememberScrollState()
var inputText by remember { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
Text(
text = "┌──[dixitcoder@device]─[~]",
color = Color.Green,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(8.dp))
for (message in messages) {
Text(
text = message,
color = Color.White,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(4.dp))
}
Spacer(modifier = Modifier.weight(1f))
// Input line
TextField(
value = inputText,
onValueChange = { inputText = it },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
textStyle = androidx.compose.ui.text.TextStyle(color = Color.White, fontFamily = FontFamily.Monospace),
placeholder = { Text("Enter command...", color = Color.Gray) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
if (inputText.isNotBlank()) {
onCommandEntered(inputText)
inputText = ""
keyboardController?.hide()
}
}
)
)
}
}
Top comments (0)