diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/README.md b/README.md index 71d1e5b..2f2f91a 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt index 48d09d4..19ffdeb 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardActivity.kt @@ -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, navController: NavHostController) { }, icon = { Icon( - screen.icon, + painterResource(screen.icon), + modifier = Modifier.size(24.dp), contentDescription = stringResource(screen.resourceId) ) }, diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt index ac45fce..f0239dd 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/DashboardViewModel.kt @@ -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() } } \ No newline at end of file diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt index 3e41b0c..1622c3d 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Dashboard.kt @@ -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,143 +44,108 @@ 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() - Column( - horizontalAlignment = Alignment.CenterHorizontally + 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( - modifier = Modifier - .fillMaxWidth() - .height(100.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center) { - Text("Hello ${user.username}!", fontSize = 28.sp) - } - - Row( - modifier = Modifier.width(340.dp) + modifier = Modifier.verticalScroll(scrollState) ) { - Card( + Column( 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) - ) - } + .height(100.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center) { + Text("Hello ${user.username}!", fontSize = 28.sp) } - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .weight(1f), + + Row( + modifier = Modifier.width(340.dp) ) { - 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" + Card( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .weight(1f), + colors = CardDefaults.cardColors( + containerColor = balanceCardColor(user.credit) ) - Spacer( - modifier = Modifier + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.baseline_monetization_on_24), + contentDescription = "Balance" + ) + Spacer(modifier = Modifier .fillMaxWidth() .weight(1f) - ) - Text( - text = formatTime(remainingTime), - fontSize = 22.sp - ) + ) + Text( + text = "$%.2f".format(user.credit), + fontSize = 22.sp, + color = balanceColor(user.credit, user.balanceThreshold) + ) + } } - } - } - - 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 + Card( + 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 + .padding(16.dp) + .weight(1f), ) { - 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 - ) + 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 + ) + } } } - } - Column( - modifier = Modifier.width(340.dp) - ) { + 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)) + } } } } diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt index 8be9036..9c4d2b4 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Instances.kt @@ -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() } + ) + } + + } + } + } } } } diff --git a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Screen.kt b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Screen.kt index 214c6f5..33aed95 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Screen.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/dashboard/screen/Screen.kt @@ -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) } diff --git a/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt b/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt index 4752b7c..8374045 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/login/LoginActivity.kt @@ -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") } diff --git a/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt b/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt index f952600..01f7d8a 100644 --- a/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt +++ b/app/src/main/java/eu/m724/vastapp/activity/login/LoginViewModel.kt @@ -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) }) ) diff --git a/app/src/main/res/drawable/baseline_account_balance_wallet_24.xml b/app/src/main/res/drawable/baseline_account_balance_wallet_24.xml deleted file mode 100644 index 83e9d26..0000000 --- a/app/src/main/res/drawable/baseline_account_balance_wallet_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_help_outline_24.xml b/app/src/main/res/drawable/baseline_help_outline_24.xml new file mode 100644 index 0000000..c459777 --- /dev/null +++ b/app/src/main/res/drawable/baseline_help_outline_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_monetization_on_24.xml b/app/src/main/res/drawable/baseline_monetization_on_24.xml new file mode 100644 index 0000000..4a01dcf --- /dev/null +++ b/app/src/main/res/drawable/baseline_monetization_on_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/house_solid.xml b/app/src/main/res/drawable/house_solid.xml new file mode 100644 index 0000000..93d7a8b --- /dev/null +++ b/app/src/main/res/drawable/house_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/server_solid.xml b/app/src/main/res/drawable/server_solid.xml new file mode 100644 index 0000000..ca66738 --- /dev/null +++ b/app/src/main/res/drawable/server_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08818f7..980d0c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,4 +6,5 @@ Dashboard Billing Instances + Help \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b095437..c0fc87d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }