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

429 lines
16 KiB
Kotlin
Raw Normal View History

2024-10-26 18:07:40 +00:00
package ui
2024-10-28 23:23:32 +00:00
import ai.djl.ndarray.NDArray
2024-10-26 18:07:40 +00:00
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
2024-10-28 23:23:32 +00:00
import androidx.compose.foundation.layout.Arrangement
2024-10-28 00:46:18 +00:00
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
2024-10-28 23:23:32 +00:00
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
2024-10-26 18:07:40 +00:00
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
2024-10-28 00:46:18 +00:00
import androidx.compose.foundation.rememberScrollState
2024-10-26 18:07:40 +00:00
import androidx.compose.foundation.shape.CircleShape
2024-10-28 00:46:18 +00:00
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Icon
2024-10-26 18:07:40 +00:00
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
2024-10-28 00:46:18 +00:00
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Check
2024-10-28 23:23:32 +00:00
import androidx.compose.material.icons.filled.CheckCircle
2024-10-28 00:46:18 +00:00
import androidx.compose.material.icons.filled.Create
2024-10-28 23:23:32 +00:00
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Save
2024-10-26 18:07:40 +00:00
import androidx.compose.runtime.Composable
2024-10-28 00:46:18 +00:00
import androidx.compose.runtime.MutableState
2024-10-26 18:07:40 +00:00
import androidx.compose.runtime.mutableStateOf
2024-10-28 00:46:18 +00:00
import androidx.compose.ui.Alignment
2024-10-26 18:07:40 +00:00
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
2024-10-28 00:46:18 +00:00
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
2024-10-26 18:07:40 +00:00
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
2024-10-28 00:46:18 +00:00
import transparentButton
2024-10-28 23:23:32 +00:00
import ui.Profiles.Companion.Dialogs.Companion.activateDialog
import ui.Profiles.Companion.Dialogs.Companion.deleteDialog
import ui.Profiles.Companion.Entities.Profile
2024-10-26 18:07:40 +00:00
import ui.screens.ProfileDetails
import ui.screens.ProfilesManager
import ui.screens.Settings
2024-10-28 00:46:18 +00:00
import java.io.File
2024-10-26 18:07:40 +00:00
import java.io.FileInputStream
2024-10-28 00:46:18 +00:00
import java.io.FileNotFoundException
2024-10-26 18:07:40 +00:00
import java.io.FileOutputStream
import java.nio.file.Paths
2024-10-28 23:23:32 +00:00
import java.util.UUID
2024-10-26 18:07:40 +00:00
import kotlin.io.path.absolutePathString
class Profiles {
companion object {
2024-10-28 00:46:18 +00:00
val activeProfile = mutableStateOf(getCurrentProfile())
val profiles = mutableStateOf(getProfiles())
2024-10-28 23:23:32 +00:00
val showActivateDialog = mutableStateOf(false)
val showDeleteDialog = mutableStateOf(false)
val selectedProfile = mutableStateOf(Profile())
2024-10-28 00:46:18 +00:00
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>
}
2024-10-26 18:07:40 +00:00
2024-10-28 23:23:32 +00:00
@OptIn(ExperimentalSerializationApi::class)
fun load(profile: File?): Profile {
var file: Profile
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
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
}
)
}
2024-10-28 00:46:18 +00:00
}
2024-10-28 23:23:32 +00:00
@Composable
fun activateDialog(profile: Profile) {
editDialog(
2024-10-28 00:46:18 +00:00
title = "Confirm Activation",
2024-10-28 23:23:32 +00:00
text = "Activate ${profile.name}?",
flag = showActivateDialog,
2024-10-28 00:46:18 +00:00
confirmClick = {
2024-10-28 23:23:32 +00:00
activeProfile.value = profile
Settings.userSettings.setProperty("current_profile", profile.name)
2024-10-28 00:46:18 +00:00
Settings.userSettings.save()
2024-10-28 23:23:32 +00:00
showActivateDialog.value = false
}
2024-10-28 00:46:18 +00:00
)
2024-10-28 23:23:32 +00:00
2024-10-28 00:46:18 +00:00
}
2024-10-28 23:23:32 +00:00
@Composable
fun deleteDialog(profile: Profile) {
val navigator = LocalNavigator.currentOrThrow
editDialog(
2024-10-28 00:46:18 +00:00
title = "Confirm Deletion",
2024-10-28 23:23:32 +00:00
text = "Delete ${profile.name}?",
flag = showDeleteDialog,
2024-10-28 00:46:18 +00:00
confirmClick = {
2024-10-28 23:23:32 +00:00
File(getProfileLocation(profile.name)).delete()
profiles.value = getProfiles()
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
if (activeProfile.value == profile) {
2024-10-28 00:46:18 +00:00
Settings.userSettings.remove("current_profile")
Settings.userSettings.save()
activeProfile.value = getCurrentProfile()
}
2024-10-28 23:23:32 +00:00
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"
)
}
2024-10-28 00:46:18 +00:00
},
2024-10-28 23:23:32 +00:00
dismissButton = {
Button(
onClick = cancelClick
) {
Icon(
imageVector = Icons.Filled.Cancel,
contentDescription = "Cancel"
)
}
2024-10-28 00:46:18 +00:00
}
)
}
}
2024-10-28 23:23:32 +00:00
}
/**
*
*/
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
)
}
}
}
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
@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++
}
}
}
2024-10-28 00:46:18 +00:00
}
2024-10-28 23:23:32 +00:00
/**
*
*/
class Buttons {
companion object {
@Composable
fun createProfile(modifier: Modifier) {
val navigator = LocalNavigator.currentOrThrow
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
Box(
modifier = modifier
.background(MaterialTheme.colors.primary, CircleShape)
2024-10-28 00:46:18 +00:00
) {
2024-10-28 23:23:32 +00:00
transparentButton(
modifier = Modifier,
icon = Icons.Filled.Create,
contentDescription = "Create new profile",
onClick = {
navigator.push(ProfileDetails())
},
2024-10-28 00:46:18 +00:00
)
}
2024-10-28 23:23:32 +00:00
}
@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
2024-10-28 00:46:18 +00:00
Button(
2024-10-28 23:23:32 +00:00
onClick = {
if (profile.name == "{NO NAME}") {
profile.name = UUID.randomUUID().toString()
}
save(profile)
navigator.pop()
}
2024-10-28 00:46:18 +00:00
) {
Icon(
2024-10-28 23:23:32 +00:00
imageVector = Icons.Filled.Save,
contentDescription = "Save Profile"
2024-10-28 00:46:18 +00:00
)
}
}
2024-10-28 23:23:32 +00:00
@Composable
fun deleteButton(profile: Profile) {
Button(
onClick = {
val file = File(getProfileLocation(profile.name))
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
if (file.exists()) {
showDeleteDialog.value = true
2024-10-28 00:46:18 +00:00
}
}
2024-10-28 23:23:32 +00:00
) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = "Delete Profile"
)
}
}
2024-10-28 00:46:18 +00:00
2024-10-28 23:23:32 +00:00
@Composable
fun activateButton(profile: Profile) {
Button(
onClick = {
showActivateDialog.value = true
}
) {
Icon(
imageVector = Icons.Filled.CheckCircle,
contentDescription = "Activate Profile"
)
}
}
2024-10-26 18:07:40 +00:00
}
}
}
}