mia/src/main/kotlin/ui/Profiles.kt

429 lines
16 KiB
Kotlin

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<Profile> {
val files = File(Paths.get("profiles/").absolutePathString()).listFiles()
files.ifEmpty {
Settings.userSettings.remove("current_profile")
}
return files.map { file ->
load(file)
} as MutableList<Profile>
}
@OptIn(ExperimentalSerializationApi::class)
fun load(profile: File?): Profile {
var file: Profile
try {
val stream = FileInputStream(profile!!)
file = Json.decodeFromStream<Profile>(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<Command>? = null
)
@Serializable
data class Command(
@SerialName("wording_Variants")
val wordingVariants: List<NDArray>? = null,
val triggers: List<String>? = null
)
@Serializable
data class WordingVariant(
val wording: String,
val embedding: NDArray? = null
)
}
/**
*
*/
class Dialogs {
companion object {
@Composable
fun editDialog(flag: MutableState<Boolean>, 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"
)
}
}
}
}
}
}