DEV Community

dixit patel
dixit patel

Posted on

✅ Kotlin Code (Jetpack Compose UI):kotlin

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.

  1. - Commands pwd, ls, cd, cat, echo, date, mkdir, rm, and help are implemented.
  2. - Terminal UI shows history of commands + output.
  3. - User can type commands at bottom input field.
  4. - 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()
                    }
                }
            )
        )
    }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)