make it work

This commit is contained in:
Minecon724 2024-07-25 18:02:38 +02:00
parent 0ca0cbe723
commit 127a31841e
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
16 changed files with 234 additions and 137 deletions

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View file

@ -13,4 +13,6 @@ I'm making this to learn stuff please don't rely on this app
- material you supported
- dashboard
home and instances icons are from font awesome
TODO

View file

@ -7,6 +7,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
@ -15,7 +16,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
@ -102,7 +105,8 @@ fun MyNavigationBar(items: List<Screen>, navController: NavHostController) {
},
icon = {
Icon(
screen.icon,
painterResource(screen.icon),
modifier = Modifier.size(24.dp),
contentDescription = stringResource(screen.resourceId)
)
},

View file

@ -32,7 +32,7 @@ class DashboardViewModel(private val _user: User, val vastApi: VastApi) : ViewMo
}
}
fun refreshData() {
fun refresh() {
val request = vastApi.buildRequest(
ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user ->
@ -41,5 +41,8 @@ class DashboardViewModel(private val _user: User, val vastApi: VastApi) : ViewMo
_uiState.value = _uiState.value.copy(isRefreshing = false, error = apiFailure.errorMessage)
})
)
_uiState.value = _uiState.value.copy(isRefreshing = true)
request.start()
}
}

View file

@ -1,5 +1,6 @@
package eu.m724.vastapp.activity.dashboard.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -8,15 +9,24 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@ -34,13 +44,24 @@ class Dashboard {
}
@OptIn(ExperimentalMaterial3Api::class) // for pullRefresh
@Composable
fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
var balance by rememberSaveable { mutableDoubleStateOf(user.credit) }
var remainingTime by rememberSaveable { mutableIntStateOf(0) }
val uiState by dashboardViewModel.uiState.collectAsState()
val user by remember(uiState) { derivedStateOf { uiState.user } }
val remainingTime by rememberSaveable { mutableIntStateOf(0) }
val scrollState = rememberScrollState()
PullToRefreshBox(
isRefreshing = uiState.isRefreshing,
state = rememberPullToRefreshState(),
onRefresh = { dashboardViewModel.refresh() }
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.verticalScroll(scrollState)
) {
Column(
modifier = Modifier
@ -60,7 +81,7 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
.padding(16.dp)
.weight(1f),
colors = CardDefaults.cardColors(
containerColor = balanceCardColor(balance)
containerColor = balanceCardColor(user.credit)
)
) {
Row(
@ -68,7 +89,7 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.baseline_account_balance_wallet_24),
painter = painterResource(id = R.drawable.baseline_monetization_on_24),
contentDescription = "Balance"
)
Spacer(modifier = Modifier
@ -76,9 +97,9 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
.weight(1f)
)
Text(
text = "$%.2f".format(balance),
text = "$%.2f".format(user.credit),
fontSize = 22.sp,
color = balanceColor(balance, user.balanceThreshold)
color = balanceColor(user.credit, user.balanceThreshold)
)
}
}
@ -109,68 +130,22 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
}
}
Row(
modifier = Modifier.width(340.dp)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.weight(1f),
colors = CardDefaults.cardColors(
containerColor = balanceCardColor(balance)
)
) {
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.baseline_account_balance_wallet_24),
contentDescription = "Balance"
)
Spacer(modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
Text(
text = "$%.2f".format(balance),
fontSize = 22.sp,
color = balanceColor(balance, user.balanceThreshold)
)
}
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.weight(1f),
) {
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.baseline_access_time_filled_24),
contentDescription = "Remaining time"
)
Spacer(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
Text(
text = formatTime(remainingTime),
fontSize = 22.sp
)
}
}
}
val instance = JSONObject()
instance.put("id", 234523)
instance.put("machine_id", 1121323)
instance.put("host_id", 5924)
instance.put("gpu_name", "RTX 4090")
instance.put("num_gpus", 2)
instance.put("gpu_util", 70)
instance.put("gpu_ram", 24564)
instance.put("vmem_usage", 0.339843)
Column(
modifier = Modifier.width(340.dp)
) {
InstanceCard(instance = instance, modifier = Modifier.padding(16.dp))
}
}
}
}

View file

@ -1,15 +1,23 @@
package eu.m724.vastapp.activity.dashboard.screen
import android.widget.ProgressBar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Card
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.m724.vastapp.activity.dashboard.DashboardViewModel
import org.json.JSONObject
@ -25,11 +33,61 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) {
@Composable
fun InstanceCard(instance: JSONObject, modifier: Modifier = Modifier) {
val gpuUsage = instance.getInt("gpu_util")
val vramGb = instance.getInt("gpu_ram") / 1000.0
val vramGbUsed = vramGb * instance.getDouble("vmem_usage")
Card(modifier = modifier) {
Row(
modifier = Modifier.fillMaxWidth()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(text = instance.getString("id"))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = instance.getString("id"), fontSize = 14.sp)
Text(text = "m:" + instance.getString("machine_id"), fontSize = 14.sp)
Text(text = "h:" + instance.getString("host_id"), fontSize = 14.sp)
}
Row {
Column {
Row(
modifier = Modifier.fillMaxWidth(0.5f),
verticalAlignment = Alignment.Bottom
) {
Text(text = instance.getString("gpu_name"), fontSize = 24.sp)
if (instance.getInt("num_gpus") > 1) {
Text(text = "x" + instance.getString("num_gpus"), modifier = Modifier.padding(start = 2.dp))
}
}
Row(
modifier = Modifier.fillMaxWidth(0.5f)
) {
Column(
modifier = Modifier.fillMaxWidth().weight(1f)
) {
Text(text = "GPU: $gpuUsage%", fontSize = 12.sp)
LinearProgressIndicator(
progress = { gpuUsage / 100f }
)
}
Column(
modifier = Modifier.fillMaxWidth().weight(1f)
) {
Text(text = "%.1f / %.1f G".format(vramGbUsed, vramGb), fontSize = 12.sp)
LinearProgressIndicator(
progress = { instance.getDouble("vmem_usage").toFloat() }
)
}
}
}
}
}
}
}

View file

@ -1,15 +1,21 @@
package eu.m724.vastapp.activity.dashboard.screen
import android.media.Image
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material.icons.outlined.ShoppingCart
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import eu.m724.vastapp.R
sealed class Screen(val route: String, @StringRes val resourceId: Int, val icon: ImageVector) {
object Dashboard : Screen("dashboard", R.string.nav_dashboard, Icons.Outlined.Home)
object Instances : Screen("instances", R.string.nav_instances, Icons.Outlined.Menu)
object Billing : Screen("billing", R.string.nav_billing, Icons.Outlined.ShoppingCart)
sealed class Screen(val route: String, @StringRes val resourceId: Int, val icon: Int) {
data object Dashboard : Screen("dashboard", R.string.nav_dashboard, R.drawable.house_solid)
data object Instances : Screen("instances", R.string.nav_instances, R.drawable.server_solid)
data object Billing : Screen("billing", R.string.nav_billing, R.drawable.baseline_monetization_on_24)
data object Help : Screen("help", R.string.nav_help, R.drawable.baseline_help_outline_24)
}

View file

@ -9,7 +9,11 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColor
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -31,6 +35,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -50,6 +55,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
@ -60,6 +68,7 @@ import eu.m724.vastapp.ui.theme.VastappTheme
import eu.m724.vastapp.vastai.data.User
import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine
import org.w3c.dom.Text
import java.util.concurrent.Executors
import kotlin.random.Random
@ -136,8 +145,9 @@ fun LoginApp(loginViewModel: LoginViewModel) {
if (state) 180f else 0f
}
Column(
modifier = Modifier.width(300.dp),
modifier = Modifier.width(300.dp).animateContentSize(spring(stiffness = Spring.StiffnessMedium)), // TODO double animation
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
@ -149,6 +159,7 @@ fun LoginApp(loginViewModel: LoginViewModel) {
label = { Text(text = "API key") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
textStyle = if (uiState is LoginUiState.Success) rainbowTextStyle() else LocalTextStyle.current,
singleLine = true,
isError = loginErrorMessage != null
)
@ -188,7 +199,20 @@ fun LoginApp(loginViewModel: LoginViewModel) {
}
@Composable
fun AdvancedOptions() {
fun rainbowTextStyle(): TextStyle {
return LocalTextStyle.current.copy(brush = Brush.linearGradient(colors = listOf(
Color.Red,
Color(255,127,0),
Color.Yellow,
Color.Green,
Color.Blue,
Color(75, 0, 130), // Indigo
Color(143, 0, 255) // Violet
)))
}
@Composable
fun AdvancedOptions() { // TODO put this in viewmodel
var checked by rememberSaveable { mutableStateOf(true) }
var clicks by rememberSaveable { mutableIntStateOf(0) }
var checkboxLabel by rememberSaveable { mutableStateOf("here's a checkbox for you") }

View file

@ -4,6 +4,7 @@ import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.m724.vastapp.vastai.ApiFailure
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi
@ -33,7 +34,7 @@ class LoginViewModel(val cronetEngine: CronetEngine, val executor: Executor) : V
_uiState.value = LoginUiState.Success(user)
}, { apiFailure ->
_uiState.value = LoginUiState.Idle
_error.value = apiFailure.errorMessage
_error.postValue(apiFailure.errorMessage)
})
)

View file

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13.41,18.09L13.41,20h-2.67v-1.93c-1.71,-0.36 -3.16,-1.46 -3.27,-3.4h1.96c0.1,1.05 0.82,1.87 2.65,1.87 1.96,0 2.4,-0.98 2.4,-1.59 0,-0.83 -0.44,-1.61 -2.67,-2.14 -2.48,-0.6 -4.18,-1.62 -4.18,-3.67 0,-1.72 1.39,-2.84 3.11,-3.21L10.74,4h2.67v1.95c1.86,0.45 2.79,1.86 2.85,3.39L14.3,9.34c-0.05,-1.11 -0.64,-1.87 -2.22,-1.87 -1.5,0 -2.4,0.68 -2.4,1.64 0,0.84 0.65,1.39 2.67,1.91s4.18,1.39 4.18,3.91c-0.01,1.83 -1.38,2.83 -3.12,3.16z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="576dp"
android:height="512dp"
android:viewportWidth="576"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M575.8,255.5c0,18 -15,32.1 -32,32.1l-32,0 0.7,160.2c0,2.7 -0.2,5.4 -0.5,8.1l0,16.2c0,22.1 -17.9,40 -40,40l-16,0c-1.1,0 -2.2,0 -3.3,-0.1c-1.4,0.1 -2.8,0.1 -4.2,0.1L416,512l-24,0c-22.1,0 -40,-17.9 -40,-40l0,-24 0,-64c0,-17.7 -14.3,-32 -32,-32l-64,0c-17.7,0 -32,14.3 -32,32l0,64 0,24c0,22.1 -17.9,40 -40,40l-24,0 -31.9,0c-1.5,0 -3,-0.1 -4.5,-0.2c-1.2,0.1 -2.4,0.2 -3.6,0.2l-16,0c-22.1,0 -40,-17.9 -40,-40l0,-112c0,-0.9 0,-1.9 0.1,-2.8l0,-69.7 -32,0c-18,0 -32,-14 -32,-32.1c0,-9 3,-17 10,-24L266.4,8c7,-7 15,-8 22,-8s15,2 21,7L564.8,231.5c8,7 12,15 11,24z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M64,32C28.7,32 0,60.7 0,96l0,64c0,35.3 28.7,64 64,64l384,0c35.3,0 64,-28.7 64,-64l0,-64c0,-35.3 -28.7,-64 -64,-64L64,32zM344,104a24,24 0,1 1,0 48,24 24,0 1,1 0,-48zM392,128a24,24 0,1 1,48 0,24 24,0 1,1 -48,0zM64,288c-35.3,0 -64,28.7 -64,64l0,64c0,35.3 28.7,64 64,64l384,0c35.3,0 64,-28.7 64,-64l0,-64c0,-35.3 -28.7,-64 -64,-64L64,288zM344,360a24,24 0,1 1,0 48,24 24,0 1,1 0,-48zM400,384a24,24 0,1 1,48 0,24 24,0 1,1 -48,0z"/>
</vector>

View file

@ -6,4 +6,5 @@
<string name="nav_dashboard">Dashboard</string>
<string name="nav_billing">Billing</string>
<string name="nav_instances">Instances</string>
<string name="nav_help">Help</string>
</resources>

View file

@ -7,13 +7,14 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
lifecycleRuntimeKtx = "2.8.3"
activityCompose = "1.9.0"
lifecycleRuntimeKtx = "2.8.4"
activityCompose = "1.9.1"
composeBom = "2024.06.00"
playServicesCronet = "18.1.0"
navigationCompose = "2.7.7"
secretsGradlePlugin = "2.0.1"
runtimeLivedata = "1.6.8"
material3 = "1.3.0-beta05" # for pulltorefresh
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -31,7 +32,7 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
play-services-cronet = { module = "com.google.android.gms:play-services-cronet", version.ref = "playServicesCronet" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }