package ui import ai.djl.ndarray.NDArray import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.AlertDialog import androidx.compose.material.Button import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Create import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Save import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.encodeToStream import transparentButton import ui.Profiles.Companion.Dialogs.Companion.activateDialog import ui.Profiles.Companion.Dialogs.Companion.deleteDialog import ui.Profiles.Companion.Entities.Profile import ui.screens.ProfileDetails import ui.screens.ProfilesManager import ui.screens.Settings import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException import java.io.FileOutputStream import java.nio.file.Paths import java.util.UUID import kotlin.io.path.absolutePathString class Profiles { companion object { val activeProfile = mutableStateOf(getCurrentProfile()) val profiles = mutableStateOf(getProfiles()) val showActivateDialog = mutableStateOf(false) val showDeleteDialog = mutableStateOf(false) val selectedProfile = mutableStateOf(Profile()) fun getCurrentProfile() = load(Settings.userSettings.getProperty("current_profile")) fun getProfileLocation(profile: String?) = Paths.get("profiles/$profile.profile").absolutePathString() fun getProfiles(): MutableList { val files = File(Paths.get("profiles/").absolutePathString()).listFiles() files.ifEmpty { Settings.userSettings.remove("current_profile") } return files.map { file -> load(file) } as MutableList } @OptIn(ExperimentalSerializationApi::class) fun load(profile: File?): Profile { var file: Profile try { val stream = FileInputStream(profile!!) file = Json.decodeFromStream(stream).copy(name = profile.nameWithoutExtension) stream.close() } catch (ne: FileNotFoundException) { file = Profile(name = "Not Selected") } catch (je: SerializationException) { file = Profile(name = profile!!.nameWithoutExtension) } return file //TODO: Add logging messages } fun load(profile: String?): Profile = load(File(getProfileLocation(profile))) @OptIn(ExperimentalSerializationApi::class) fun save(profile: Profile) { val stream = FileOutputStream(getProfileLocation(profile.name)) Json.encodeToStream(profile, stream) stream.flush() stream.close() } /** * */ class Entities { @Serializable data class Profile( var name: String = "{NO NAME}", @SerialName("ai_name") var aiName: String = "{NO NAME}", @SerialName("program_name") var programName: String = "{NO NAME}", @SerialName("program_path") var programPath: String = "{NO NAME}", @SerialName("commands") var commands: List? = null ) @Serializable data class Command( @SerialName("wording_Variants") val wordingVariants: List? = null, val triggers: List? = null ) @Serializable data class WordingVariant( val wording: String, val embedding: NDArray? = null ) } /** * */ class Dialogs { companion object { @Composable fun editDialog(flag: MutableState, title: String, text: String, confirmClick: () -> Unit) { if (flag.value) { profileDialog( title = title, text = text, confirmClick = confirmClick, cancelClick = { flag.value = false } ) } } @Composable fun activateDialog(profile: Profile) { editDialog( title = "Confirm Activation", text = "Activate ${profile.name}?", flag = showActivateDialog, confirmClick = { activeProfile.value = profile Settings.userSettings.setProperty("current_profile", profile.name) Settings.userSettings.save() showActivateDialog.value = false } ) } @Composable fun deleteDialog(profile: Profile) { val navigator = LocalNavigator.currentOrThrow editDialog( title = "Confirm Deletion", text = "Delete ${profile.name}?", flag = showDeleteDialog, confirmClick = { File(getProfileLocation(profile.name)).delete() profiles.value = getProfiles() if (activeProfile.value == profile) { Settings.userSettings.remove("current_profile") Settings.userSettings.save() activeProfile.value = getCurrentProfile() } showDeleteDialog.value = false if (navigator.lastItem is ProfileDetails) { navigator.pop() } } ) } @Composable fun profileDialog(title: String, text: String, confirmClick: () -> Unit, cancelClick: () -> Unit) { AlertDialog( title = { Text(text = title) }, text = { Text(text = text) }, onDismissRequest = { cancelClick }, confirmButton = { Button( onClick = confirmClick ) { Icon( imageVector = Icons.Filled.Check, contentDescription = "Accept" ) } }, dismissButton = { Button( onClick = cancelClick ) { Icon( imageVector = Icons.Filled.Cancel, contentDescription = "Cancel" ) } } ) } } } /** * */ class Lists { companion object { @Composable fun profileList() { profiles.value = getProfiles() Column( modifier = Modifier .padding(horizontal = 25.dp) .padding(top = 10.dp, bottom = 50.dp) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.End, ) { profileListHeader() profileListEntries() activateDialog(selectedProfile.value) deleteDialog(selectedProfile.value) } } @Composable fun profileListItem(profileName: String, programName: String, aiName: String, modifier: Modifier) { Row( modifier = modifier .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { listOf(profileName, programName, aiName).forEach { name -> Text( modifier = Modifier .weight(1f), text = name, textAlign = TextAlign.Center ) } } } @Composable fun profileListHeader() { profileListItem( modifier = Modifier .background(MaterialTheme.colors.onBackground.copy(alpha = 0.05f), CircleShape), profileName = "Profile Name", programName = "Program Name", aiName = "AI Name" ) } @OptIn(ExperimentalFoundationApi::class) @Composable fun profileListEntries() { val navigator = LocalNavigator.currentOrThrow var count = 1 profiles.value.forEach { profile -> val backgroundColor = if (count % 2 == 0) MaterialTheme.colors.onBackground.copy(alpha = 0.05f) else MaterialTheme.colors.background profileListItem( modifier = Modifier .background(backgroundColor, CircleShape) .combinedClickable( onClick = { navigator.push(ProfileDetails(profile)) }, onDoubleClick = { selectedProfile.value = profile showActivateDialog.value = true }, onLongClick = { selectedProfile.value = profile showDeleteDialog.value = true }, ), profileName = profile.name, programName = profile.programName, aiName = profile.aiName, ) count++ } } } } /** * */ class Buttons { companion object { @Composable fun createProfile(modifier: Modifier) { val navigator = LocalNavigator.currentOrThrow Box( modifier = modifier .background(MaterialTheme.colors.primary, CircleShape) ) { transparentButton( modifier = Modifier, icon = Icons.Filled.Create, contentDescription = "Create new profile", onClick = { navigator.push(ProfileDetails()) }, ) } } @OptIn(ExperimentalFoundationApi::class) @Composable fun currentProfile(modifier: Modifier) { val navigator = LocalNavigator.currentOrThrow Text( modifier = modifier .background(MaterialTheme.colors.primary, CircleShape) .padding(5.dp) .width(150.dp) .combinedClickable( onClick = { navigator.push(ProfileDetails(activeProfile.value)) }, onDoubleClick = { if (navigator.lastItem !is ProfilesManager) { navigator.push(ProfilesManager()) } } ), color = MaterialTheme.colors.onPrimary, textAlign = TextAlign.Center, text = activeProfile.value.name ) } @Composable fun saveButton(profile: Profile) { val navigator = LocalNavigator.currentOrThrow Button( onClick = { if (profile.name == "{NO NAME}") { profile.name = UUID.randomUUID().toString() } save(profile) navigator.pop() } ) { Icon( imageVector = Icons.Filled.Save, contentDescription = "Save Profile" ) } } @Composable fun deleteButton(profile: Profile) { Button( onClick = { val file = File(getProfileLocation(profile.name)) if (file.exists()) { showDeleteDialog.value = true } } ) { Icon( imageVector = Icons.Filled.Delete, contentDescription = "Delete Profile" ) } } @Composable fun activateButton(profile: Profile) { Button( onClick = { showActivateDialog.value = true } ) { Icon( imageVector = Icons.Filled.CheckCircle, contentDescription = "Activate Profile" ) } } } } } }