Compare commits
12 commits
67ffa975cb
...
c5a401c1e5
Author | SHA1 | Date | |
---|---|---|---|
c5a401c1e5 | |||
a5d93da6ef | |||
a559f433db | |||
2fc83c4d5b | |||
0b4bc2e524 | |||
643006f9c4 | |||
2ead3c054a | |||
bf240c4203 | |||
9c1741f769 | |||
3e1631908a | |||
b0d86f0a67 | |||
8dab1d7691 |
11 changed files with 240 additions and 105 deletions
|
@ -62,7 +62,6 @@ android {
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
|
|
|
@ -115,10 +115,10 @@ class DashboardViewModel(
|
||||||
|
|
||||||
fun toggleInstance(instance: RentedInstance) {
|
fun toggleInstance(instance: RentedInstance) {
|
||||||
val deferred =
|
val deferred =
|
||||||
if (instance.status == "running") {
|
if (instance.isRunning()) {
|
||||||
vastApi.startInstance(instance.rentalId)
|
|
||||||
} else {
|
|
||||||
vastApi.stopInstance(instance.rentalId)
|
vastApi.stopInstance(instance.rentalId)
|
||||||
|
} else {
|
||||||
|
vastApi.startInstance(instance.rentalId)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -144,5 +144,4 @@ class DashboardViewModel(
|
||||||
}
|
}
|
||||||
} // TODO once again these methods share some code and more probably will so why not move the shared stuff
|
} // TODO once again these methods share some code and more probably will so why not move the shared stuff
|
||||||
// OR not refresh but refresh only instances or even better don't refresh instances but delete or edit that one
|
// OR not refresh but refresh only instances or even better don't refresh instances but delete or edit that one
|
||||||
|
|
||||||
}
|
}
|
|
@ -72,85 +72,101 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
// balance card
|
// balance card
|
||||||
Card(
|
BalanceCard(balance = user.credit, balanceWarning = user.balanceThreshold)
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = balanceCardColor(user.credit)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(16.dp, 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
painter = painterResource(id = R.drawable.baseline_monetization_on_24),
|
|
||||||
contentDescription = stringResource(id = R.string.balance)
|
|
||||||
)
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier.width(12.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "$%.2f".format(user.credit),
|
|
||||||
fontSize = 22.sp,
|
|
||||||
color = balanceColor(user.credit, user.balanceThreshold)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// time card
|
// time card
|
||||||
Card(
|
if (rentedInstances.isNotEmpty())
|
||||||
modifier = Modifier
|
RemainingTimeCard(remainingTime = remainingTime)
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(16.dp, 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
painter = painterResource(id = R.drawable.baseline_access_time_filled_24),
|
|
||||||
contentDescription = stringResource(id = R.string.time_left)
|
|
||||||
)
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier.width(12.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = formatTime(remainingTime),
|
|
||||||
fontSize = 22.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// instances
|
// instances
|
||||||
Card(
|
InstancesCard(rentedInstancesCount = rentedInstances.size)
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(16.dp, 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
painter = painterResource(id = R.drawable.server_solid),
|
|
||||||
contentDescription = stringResource(id = R.string.rented_instances)
|
|
||||||
)
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier.width(12.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = rentedInstances.size.toString(),
|
|
||||||
fontSize = 22.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RemainingTimeCard(remainingTime: Int) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp, 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
painter = painterResource(id = R.drawable.baseline_access_time_filled_24),
|
||||||
|
contentDescription = stringResource(id = R.string.time_left)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.width(12.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = formatTime(remainingTime),
|
||||||
|
fontSize = 22.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BalanceCard(balance: Double, balanceWarning: Double) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = balanceCardColor(balance)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp, 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
painter = painterResource(id = R.drawable.baseline_monetization_on_24),
|
||||||
|
contentDescription = stringResource(id = R.string.balance)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.width(12.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "$%.2f".format(balance),
|
||||||
|
fontSize = 22.sp,
|
||||||
|
color = balanceColor(balance, balanceWarning)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstancesCard(rentedInstancesCount: Int) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp, 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
painter = painterResource(id = R.drawable.server_solid),
|
||||||
|
contentDescription = stringResource(id = R.string.rented_instances)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.width(12.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = rentedInstancesCount.toString(),
|
||||||
|
fontSize = 22.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun balanceCardColor(balance: Double): Color {
|
fun balanceCardColor(balance: Double): Color {
|
||||||
return if (balance > 0) Color.Unspecified else MaterialTheme.colorScheme.errorContainer
|
return if (balance > 0) Color.Unspecified else MaterialTheme.colorScheme.errorContainer
|
||||||
|
|
|
@ -2,13 +2,14 @@ package eu.m724.vastapp.activity.dashboard.screen
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ContextualFlowRow
|
import androidx.compose.foundation.layout.ContextualFlowRow
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
@ -18,14 +19,17 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -75,15 +79,19 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Box(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(100.dp),
|
.height(100.dp),
|
||||||
contentAlignment = Alignment.Center
|
verticalArrangement = Arrangement.Bottom,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.no_instances)
|
text = stringResource(id = R.string.no_instances)
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.rent_on_website)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,13 +107,34 @@ fun RentedInstanceCard(
|
||||||
deleteButtonClick: (RentedInstance) -> Unit,
|
deleteButtonClick: (RentedInstance) -> Unit,
|
||||||
) {
|
) {
|
||||||
val instance by remember(rentedInstance) { derivedStateOf { rentedInstance.instance } }
|
val instance by remember(rentedInstance) { derivedStateOf { rentedInstance.instance } }
|
||||||
val label by remember(instance) { derivedStateOf {
|
|
||||||
rentedInstance.label ?: instance.machine.gpu.model
|
val dialogOpen = remember { mutableStateOf(false) }
|
||||||
} }
|
|
||||||
|
if (dialogOpen.value) {
|
||||||
|
InstanceDeleteDialog(
|
||||||
|
instance = rentedInstance,
|
||||||
|
onConfirm = {
|
||||||
|
dialogOpen.value = false
|
||||||
|
deleteButtonClick(rentedInstance)
|
||||||
|
},
|
||||||
|
onClose = { dialogOpen.value = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Card(modifier = modifier) {
|
Card(modifier = modifier) {
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
Row(
|
||||||
Text(label, fontSize = 22.sp)
|
modifier = Modifier
|
||||||
|
.height(IntrinsicSize.Min)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.SpaceEvenly // TODO I think the label is too low
|
||||||
|
) {
|
||||||
|
Text(rentedInstance.getName(), fontSize = 22.sp)
|
||||||
|
Text(rentedInstance.status, fontSize = 14.sp)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
@ -115,7 +144,7 @@ fun RentedInstanceCard(
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
onClick = { deleteButtonClick(rentedInstance) },
|
onClick = { dialogOpen.value = true },
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
|
@ -130,9 +159,9 @@ fun RentedInstanceCard(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
onClick = { actionButtonClick(rentedInstance) },
|
onClick = { actionButtonClick(rentedInstance) },
|
||||||
enabled = rentedInstance.status == rentedInstance.targetStatus
|
enabled = !rentedInstance.isChangingState()
|
||||||
) {
|
) {
|
||||||
if (rentedInstance.status == "running") {
|
if (rentedInstance.isRunning()) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
painter = painterResource(id = R.drawable.baseline_stop_24),
|
painter = painterResource(id = R.drawable.baseline_stop_24),
|
||||||
|
@ -177,9 +206,52 @@ fun RentedInstanceCard(
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||||
contentDescription = "Details about instance $label"
|
contentDescription = "Details about instance ${rentedInstance.getName()}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstanceDeleteDialog(
|
||||||
|
instance: RentedInstance,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { onClose() },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onConfirm() }
|
||||||
|
) {
|
||||||
|
Text("Confirm")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onClose() }
|
||||||
|
) {
|
||||||
|
Text("Dismiss")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.instance_confirm_delete
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.instance_confirm_delete_text,
|
||||||
|
instance.rentalId,
|
||||||
|
instance.getName()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -60,8 +60,6 @@ import eu.m724.vastapp.activity.dashboard.DashboardActivity
|
||||||
import eu.m724.vastapp.ui.theme.VastappTheme
|
import eu.m724.vastapp.ui.theme.VastappTheme
|
||||||
import eu.m724.vastapp.vastai.data.User
|
import eu.m724.vastapp.vastai.data.User
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.chromium.net.CronetEngine
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
class LoginActivity : ComponentActivity() {
|
class LoginActivity : ComponentActivity() {
|
||||||
private lateinit var dashboardLauncher: ActivityResultLauncher<Intent>
|
private lateinit var dashboardLauncher: ActivityResultLauncher<Intent>
|
||||||
|
@ -73,21 +71,25 @@ class LoginActivity : ComponentActivity() {
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { _ -> finish() } // TODO re-login here
|
) { _ -> finish() } // TODO re-login here
|
||||||
|
|
||||||
val executor = Executors.newSingleThreadExecutor()
|
val loginViewModel = LoginViewModel(application)
|
||||||
val cronetEngine = CronetEngine.Builder(baseContext).build()
|
|
||||||
val loginViewModel = LoginViewModel(application, cronetEngine, executor)
|
|
||||||
|
|
||||||
|
// load api key if saved
|
||||||
|
loginViewModel.init()
|
||||||
|
|
||||||
|
// handle login errors
|
||||||
loginViewModel.error.observe(this) { errorMessage ->
|
loginViewModel.error.observe(this) { errorMessage ->
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
Toast.makeText(baseContext, errorMessage, Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, errorMessage, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle loading error
|
||||||
val loadingError = intent.getStringExtra("error")
|
val loadingError = intent.getStringExtra("error")
|
||||||
if (loadingError != null) {
|
if (loadingError != null) {
|
||||||
Toast.makeText(baseContext, loadingError, Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, loadingError, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load app if it's time
|
||||||
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope
|
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope
|
||||||
loginViewModel.uiState.collect { state ->
|
loginViewModel.uiState.collect { state ->
|
||||||
if (state is LoginUiState.Success) {
|
if (state is LoginUiState.Success) {
|
||||||
|
|
|
@ -5,19 +5,17 @@ import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import eu.m724.vastapp.BuildConfig
|
||||||
import eu.m724.vastapp.VastApplication
|
import eu.m724.vastapp.VastApplication
|
||||||
|
import eu.m724.vastapp.vastai.Account
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.chromium.net.CronetEngine
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
|
|
||||||
class LoginViewModel(
|
class LoginViewModel(
|
||||||
application: Application,
|
application: Application
|
||||||
private val cronetEngine: CronetEngine,
|
|
||||||
private val executor: Executor
|
|
||||||
) : AndroidViewModel(application) {
|
) : AndroidViewModel(application) {
|
||||||
private val _uiState: MutableStateFlow<LoginUiState> =
|
private val _uiState: MutableStateFlow<LoginUiState> =
|
||||||
MutableStateFlow(LoginUiState.Idle)
|
MutableStateFlow(LoginUiState.Idle)
|
||||||
|
@ -32,13 +30,19 @@ class LoginViewModel(
|
||||||
|
|
||||||
private val application = getApplication<VastApplication>()
|
private val application = getApplication<VastApplication>()
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
_apiKey.value = application.loadKey() ?: BuildConfig.VASTAI_KEY
|
||||||
|
}
|
||||||
|
|
||||||
fun tryLogin() {
|
fun tryLogin() {
|
||||||
|
application.vastApi.setApiKey(apiKey.value)
|
||||||
val userDeferred = application.vastApi.getUser()
|
val userDeferred = application.vastApi.getUser()
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val user = userDeferred.await()
|
val user = userDeferred.await()
|
||||||
application.submitKey(apiKey.value) // TODO toggle for this
|
application.submitKey(apiKey.value) // TODO toggle for this
|
||||||
|
application.account = Account(user)
|
||||||
_uiState.update { LoginUiState.Success(user) }
|
_uiState.update { LoginUiState.Success(user) }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_uiState.update { LoginUiState.Idle }
|
_uiState.update { LoginUiState.Idle }
|
||||||
|
|
|
@ -56,7 +56,7 @@ class VastApi(
|
||||||
val deferred = CompletableDeferred<Unit>()
|
val deferred = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
val request = requestMaker.buildRequest(
|
val request = requestMaker.buildRequest(
|
||||||
"/instances/$rentalId",
|
"/instances/$rentalId/", // THIS NEEDS A / AT THE END
|
||||||
JsonUrlRequestCallback(
|
JsonUrlRequestCallback(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
if (it.getBoolean("success"))
|
if (it.getBoolean("success"))
|
||||||
|
@ -77,7 +77,7 @@ class VastApi(
|
||||||
val deferred = CompletableDeferred<Unit>()
|
val deferred = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
val request = requestMaker.buildRequest(
|
val request = requestMaker.buildRequest(
|
||||||
"/instances/$rentalId",
|
"/instances/$rentalId/", // THIS NEEDS A / AT THE END
|
||||||
JsonUrlRequestCallback(
|
JsonUrlRequestCallback(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
deferred.complete(Unit)
|
deferred.complete(Unit)
|
||||||
|
@ -97,7 +97,7 @@ class VastApi(
|
||||||
val deferred = CompletableDeferred<Unit>()
|
val deferred = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
val request = requestMaker.buildRequest(
|
val request = requestMaker.buildRequest(
|
||||||
"/instances/$rentalId",
|
"/instances/$rentalId/", // THIS NEEDS A / AT THE END
|
||||||
JsonUrlRequestCallback(
|
JsonUrlRequestCallback(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
deferred.complete(Unit)
|
deferred.complete(Unit)
|
||||||
|
|
|
@ -25,7 +25,8 @@ open class StringUrlRequestCallback(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
|
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
request?.read(ByteBuffer.allocateDirect(102400))
|
val size = info?.allHeaders?.get("content-length")?.get(0)?.toIntOrNull()
|
||||||
|
request?.read(ByteBuffer.allocateDirect(size ?: 100000))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReadCompleted(
|
override fun onReadCompleted(
|
||||||
|
@ -36,13 +37,19 @@ open class StringUrlRequestCallback(
|
||||||
byteBuffer?.clear()
|
byteBuffer?.clear()
|
||||||
request?.read(byteBuffer)
|
request?.read(byteBuffer)
|
||||||
|
|
||||||
stringResponse.append(Charsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.IGNORE).decode(byteBuffer))
|
stringResponse.append(
|
||||||
|
Charsets.UTF_8.newDecoder()
|
||||||
|
.onUnmappableCharacter(CodingErrorAction.IGNORE)
|
||||||
|
.decode(byteBuffer)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
|
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
val body = stringResponse.toString()
|
val body = stringResponse.toString()
|
||||||
val statusCode = info.httpStatusCode
|
val statusCode = info.httpStatusCode
|
||||||
|
println(info.httpStatusCode.toString() + ": " + stringResponse.substring(0, stringResponse.length))
|
||||||
|
println(info.allHeaders.toString())
|
||||||
|
|
||||||
if (statusCode == 200) {
|
if (statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
|
@ -52,7 +59,7 @@ open class StringUrlRequestCallback(
|
||||||
}
|
}
|
||||||
} else if (statusCode >= 500) {
|
} else if (statusCode >= 500) {
|
||||||
onFailure(ServerError(statusCode, body))
|
onFailure(ServerError(statusCode, body))
|
||||||
} else if (statusCode == 403) {
|
} else if (statusCode == 401) {
|
||||||
onFailure(UnauthorizedException(body))
|
onFailure(UnauthorizedException(body))
|
||||||
} else {
|
} else {
|
||||||
onFailure(ClientException(body))
|
onFailure(ClientException(body))
|
||||||
|
|
|
@ -72,4 +72,34 @@ data class RentedInstance(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gpu model or label if set
|
||||||
|
* @return instance name
|
||||||
|
*/
|
||||||
|
fun getName(): String {
|
||||||
|
return label ?: instance.machine.gpu.model
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns whether the instance is running
|
||||||
|
* this is true also if the instance is stopping
|
||||||
|
* vice versa, false if stopped or starting
|
||||||
|
* @return is the instance running
|
||||||
|
*/
|
||||||
|
fun isRunning(): Boolean {
|
||||||
|
return status == "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStopping(): Boolean {
|
||||||
|
return status == "running" && targetStatus == "stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStarting(): Boolean {
|
||||||
|
return status == "exited" && targetStatus == "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isChangingState(): Boolean {
|
||||||
|
return isStopping() || isStarting()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
<string name="title_activity_login">Logowanie</string>
|
<string name="title_activity_login">Logowanie</string>
|
||||||
<string name="nav_dashboard">Kokpit</string>
|
<string name="nav_dashboard">Kokpit</string>
|
||||||
<string name="nav_billing">Płatności</string>
|
<string name="nav_billing">Płatności</string>
|
||||||
<string name="nav_instances">Maszyny</string>
|
<string name="nav_instances">Instancje</string>
|
||||||
<string name="nav_help">Pomoc</string>
|
<string name="nav_help">Pomoc</string>
|
||||||
<string name="balance">Bilans</string>
|
<string name="balance">Bilans</string>
|
||||||
<string name="greeting">Witaj %1$s!</string>
|
<string name="greeting">Witaj %1$s!</string>
|
||||||
|
@ -27,7 +27,10 @@
|
||||||
<string name="termux_not_configured">Termux nie jest skonfigurowany pod działanie z innymi aplikacjami.</string>
|
<string name="termux_not_configured">Termux nie jest skonfigurowany pod działanie z innymi aplikacjami.</string>
|
||||||
<string name="termux_open_instructions">Otwórz instrukcje na github.com</string>
|
<string name="termux_open_instructions">Otwórz instrukcje na github.com</string>
|
||||||
<string name="termux_error">Wystąpił błąd:</string>
|
<string name="termux_error">Wystąpił błąd:</string>
|
||||||
<string name="no_instances">Nie wynajmujesz żadnych maszyn</string>
|
<string name="no_instances">Nie wynajmujesz żadnych instancji</string>
|
||||||
<string name="webview_todo">(kiedyś to będzie tutaj)</string>
|
<string name="webview_todo">(kiedyś to będzie tutaj)</string>
|
||||||
<string name="termux_no_ssh">Brakuje klienta SSH na Termux</string>
|
<string name="termux_no_ssh">Brakuje klienta SSH na Termux</string>
|
||||||
|
<string name="rent_on_website">Jeszcze nie możesz wynajmować z tej aplikacji</string>
|
||||||
|
<string name="instance_confirm_delete">Potwierdź usunięcie</string>
|
||||||
|
<string name="instance_confirm_delete_text">Instancja #%1$d (%2$s)</string>
|
||||||
</resources>
|
</resources>
|
|
@ -31,4 +31,7 @@
|
||||||
<string name="webview_todo">(this will be a webview)</string>
|
<string name="webview_todo">(this will be a webview)</string>
|
||||||
<string name="no_instances">You are not renting any instances</string>
|
<string name="no_instances">You are not renting any instances</string>
|
||||||
<string name="termux_no_ssh">Missing SSH client on Termux</string>
|
<string name="termux_no_ssh">Missing SSH client on Termux</string>
|
||||||
|
<string name="rent_on_website">You can\'t rent from this app yet</string>
|
||||||
|
<string name="instance_confirm_delete">Confirm deletion</string>
|
||||||
|
<string name="instance_confirm_delete_text">Instance #%1$d (%2$s)</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue