Pith - wedge_android
wedge_android/app/src/main/java/com/vgmlr/wedge/WedgeSync.kt [9.7 kb]
Modified: 06:16:53 91 026 (18 Jun 026)
6 Days Ago
package com.vgmlr.wedge

import android.content.Context
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import java.net.Inet4Address
import java.net.NetworkInterface

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SyncViewContent(vm: MainViewModel, prefs: PreferenceManager) {
    val context = LocalContext.current
    val sharedPrefs = remember { context.getSharedPreferences("sync_prefs", Context.MODE_PRIVATE) }

    val editorTxt by prefs.textColor.collectAsState(WedgeConfig.TEXT_COLOR_DEFAULT)

    var selectedRoute by remember { mutableStateOf(sharedPrefs.getString("route", "USB") ?: "USB") }
    var selectedRole by remember { mutableStateOf(sharedPrefs.getString("role", "Server") ?: "Server") }
    val passKeyFlow by prefs.passKey.collectAsState(initial = "wedge03569")
    var localPassKey by remember { mutableStateOf<String?>(null) }
    val displayPassKey = localPassKey ?: passKeyFlow

    val syncStatusText by vm.syncStatus.collectAsState()
    val isServerRunning by vm.isServerRunning.collectAsState()

    val localIpAddress = remember {
        try {
            NetworkInterface.getNetworkInterfaces().asSequence()
                .flatMap { it.inetAddresses.asSequence() }
                .firstOrNull { !it.isLoopbackAddress && it is Inet4Address }
                ?.hostAddress ?: "Unknown"
        } catch (_: Exception) {
            "Unknown"
        }
    }

    LaunchedEffect(selectedRoute) {
        if (selectedRoute == "USB") {
            selectedRole = "Server"
        }
    }

    LaunchedEffect(selectedRoute) { sharedPrefs.edit { putString("route", selectedRoute) } }
    LaunchedEffect(selectedRole) { sharedPrefs.edit { putString("role", selectedRole) } }

    LaunchedEffect(selectedRoute) {
        vm.stopUsbServer()
    }

    val btnShape = RoundedCornerShape(6.dp)
    val labelColor = parseColor(editorTxt).copy(alpha = 0.6f)
    val fieldColor = OutlinedTextFieldDefaults.colors(
        focusedTextColor = parseColor(editorTxt),
        unfocusedTextColor = parseColor(editorTxt),
        focusedBorderColor = parseColor(editorTxt).copy(alpha = 0.5f),
        unfocusedBorderColor = parseColor(editorTxt).copy(alpha = 0.2f)
    )
    val btnColors = ButtonDefaults.buttonColors(
        containerColor = Color.Gray,
        contentColor = Color.Black
    )

    Column(modifier = Modifier.fillMaxWidth()) {
        Spacer(Modifier.height(15.dp))

        Row(
            modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start
        ) {
            Box(modifier = Modifier.width(90.dp)) {
                SyncLabel("Route", labelColor)
            }
            SyncRadioGroup(
                options = listOf("USB", "WIFI", "Bluetooth"),
                selectedOption = selectedRoute,
                onOptionSelected = { selectedRoute = it },
                textColor = parseColor(editorTxt),
                disabledOptions = listOf("Bluetooth")
            )
        }
        Spacer(Modifier.height(14.dp))

        Row(
            modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start
        ) {
            Box(modifier = Modifier.width(90.dp)) {
                SyncLabel("Role", labelColor)
            }
            SyncRadioGroup(
                options = listOf("Client", "Server"),
                selectedOption = selectedRole,
                onOptionSelected = { selectedRole = it },
                textColor = parseColor(editorTxt),
                disabledOptions = listOf("Client")
            )
        }
        Spacer(Modifier.height(14.dp))

        Row(
            modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start
        ) {
            Box(modifier = Modifier.width(90.dp)) {
                SyncLabel("Local IP", labelColor)
            }
            Text(
                text = localIpAddress,
                color = parseColor(editorTxt),
                fontSize = 15.sp
            )
        }
        Spacer(Modifier.height(14.dp))

        Row(
            modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start
        ) {
            Box(modifier = Modifier.width(90.dp)) {
                SyncLabel("Pass Key", labelColor)
            }
            OutlinedTextField(
                value = displayPassKey,
                onValueChange = {
                    localPassKey = it
                    vm.updatePassKey(it)
                },
                modifier = Modifier.fillMaxWidth(),
                colors = fieldColor
            )
        }
        Spacer(Modifier.height(14.dp))

        Row(
            modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start
        ) {
            Box(modifier = Modifier.width(90.dp)) {
                SyncLabel("Status", labelColor)
            }
            Text(
                text = syncStatusText,
                color = parseColor(editorTxt),
                fontSize = 15.sp
            )
        }
        Spacer(Modifier.height(20.dp))

        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Button(
                onClick = {
                    if (isServerRunning) {
                        vm.stopUsbServer()
                    } else {
                        if (selectedRoute == "USB" || selectedRoute == "WIFI") {
                            vm.startUsbServer()
                        }
                    }
                },
                modifier = Modifier.weight(1f).height(48.dp),
                colors = btnColors,
                shape = btnShape
            ) {
                Text(
                    text = if (isServerRunning) "Disconnect" else "Connect",
                    color = Color.Black,
                    fontSize = 16.sp
                )
            }
            Button(
                onClick = { vm.sendNoteText() },
                modifier = Modifier.weight(1f).height(48.dp),
                colors = btnColors,
                shape = btnShape
            ) {
                Text("Send", color = Color.Black, fontSize = 16.sp)
            }
        }
    }
}

@Composable
private fun SyncLabel(text: String, color: Color) {
    Text(
        text = text,
        color = color,
        fontWeight = FontWeight.Bold,
        fontSize = 16.sp,
    )
}

@Composable
private fun SyncRadioGroup(
    options: List<String>,
    selectedOption: String,
    onOptionSelected: (String) -> Unit,
    textColor: Color,
    disabledOptions: List<String> = emptyList()
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .selectableGroup(),
        horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically
    ) {
        options.forEachIndexed { index, text ->
            val interactionSource = remember { MutableInteractionSource() }
            val isEnabled = text !in disabledOptions
            Row(
                modifier = Modifier
                    .selectable(
                        selected = (text == selectedOption),
                        onClick = { onOptionSelected(text) },
                        role = Role.RadioButton,
                        interactionSource = interactionSource,
                        indication = null,
                        enabled = isEnabled
                    )
                    .padding(vertical = 4.dp),
                verticalAlignment = Alignment.CenterVertically
            ) {
                RadioButton(
                    selected = (text == selectedOption),
                    onClick = null,
                    enabled = isEnabled,
                    colors = RadioButtonDefaults.colors(
                        selectedColor = colorResource(id = R.color.dialog_color).copy(alpha = 0.8f),
                        unselectedColor = colorResource(id = R.color.dialog_color).copy(alpha = 0.6f),
                        disabledSelectedColor = colorResource(id = R.color.dialog_color).copy(alpha = 0.3f),
                        disabledUnselectedColor = colorResource(id = R.color.dialog_color).copy(alpha = 0.2f)
                    )
                )
                Text(
                    text = text,
                    color = if (isEnabled) textColor else textColor.copy(alpha = 0.5f),
                    fontSize = 15.sp,
                    modifier = Modifier.padding(start = 4.dp)
                )
            }
            if (index < options.lastIndex) {
                Spacer(Modifier.width(16.dp))
            }
        }
    }
}
Updates
OTC Applet - Linux 93.026.1
Wedge - Linux 90.026.1
Wedge - Android 90.026.1
Shim - Android 86.026.1
Kerf - Android 86.026.4
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)