Compare commits

..

8 commits

12 changed files with 90 additions and 295 deletions

1
.gitignore vendored
View file

@ -17,3 +17,4 @@ local.properties
# useless # useless
/.idea/deploymentTargetSelector.xml /.idea/deploymentTargetSelector.xml
/.idea/other.xml

View file

@ -1,263 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="direct_access_persist.xml">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="q2q" />
<option name="id" value="q2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold3" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1768" />
<option name="screenY" value="2208" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="x1q" />
<option name="id" value="x1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S20" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

View file

@ -15,4 +15,8 @@ I'm making this to learn stuff please don't rely on this app
home and instances icons are from font awesome home and instances icons are from font awesome
TODO ### TODOs
- move todos to issues
- readme
- figure out the api
- gb gib mb mib

View file

@ -1,6 +1,7 @@
package eu.m724.vastapp.activity.dashboard package eu.m724.vastapp.activity.dashboard
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@ -19,6 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@ -26,14 +30,15 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import eu.m724.vastapp.activity.dashboard.screen.Screen
import eu.m724.vastapp.activity.dashboard.screen.BillingScreen import eu.m724.vastapp.activity.dashboard.screen.BillingScreen
import eu.m724.vastapp.activity.dashboard.screen.DashboardScreen import eu.m724.vastapp.activity.dashboard.screen.DashboardScreen
import eu.m724.vastapp.activity.dashboard.screen.HelpScreen import eu.m724.vastapp.activity.dashboard.screen.HelpScreen
import eu.m724.vastapp.activity.dashboard.screen.InstancesScreen import eu.m724.vastapp.activity.dashboard.screen.InstancesScreen
import eu.m724.vastapp.activity.dashboard.screen.Screen
import eu.m724.vastapp.ui.theme.VastappTheme import eu.m724.vastapp.ui.theme.VastappTheme
import eu.m724.vastapp.vastai.VastApi import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.data.User import eu.m724.vastapp.vastai.data.User
import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine import org.chromium.net.CronetEngine
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -49,6 +54,17 @@ class DashboardActivity : ComponentActivity() {
val dashboardViewModel = DashboardViewModel(user, vastApi) val dashboardViewModel = DashboardViewModel(user, vastApi)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
dashboardViewModel.refreshError.collect {
it.forEach { errorMsg ->
Toast.makeText(baseContext, errorMsg, Toast.LENGTH_SHORT).show()
}
}
}
}
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
VastappTheme { VastappTheme {

View file

@ -1,9 +1,6 @@
package eu.m724.vastapp.activity.dashboard package eu.m724.vastapp.activity.dashboard
import eu.m724.vastapp.vastai.data.User
data class DashboardUiState( data class DashboardUiState(
val isRefreshing: Boolean, val refreshing: Int = 0
val user: User, ) {
val error: String? }
) { }

View file

@ -3,30 +3,69 @@ package eu.m724.vastapp.activity.dashboard
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import eu.m724.vastapp.vastai.ApiRoute import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
import eu.m724.vastapp.vastai.data.Instance
import eu.m724.vastapp.vastai.data.User import eu.m724.vastapp.vastai.data.User
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
class DashboardViewModel(initialUser: User, private val vastApi: VastApi) : ViewModel() { // TODO do something with the user class DashboardViewModel(initialUser: User, private val vastApi: VastApi) : ViewModel() { // TODO do something with the user
private val _uiState: MutableStateFlow<DashboardUiState> = private val _uiState: MutableStateFlow<DashboardUiState> =
MutableStateFlow(DashboardUiState(false, initialUser, null)) MutableStateFlow(DashboardUiState(0))
val uiState: StateFlow<DashboardUiState> = val uiState: StateFlow<DashboardUiState> =
_uiState.asStateFlow() _uiState.asStateFlow()
private val _rentedInstances: MutableStateFlow<List<Instance>> = MutableStateFlow(emptyList())
val rentedInstances: StateFlow<List<Instance>> = _rentedInstances.asStateFlow()
private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser)
val user: StateFlow<User> = _user.asStateFlow()
private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
fun refresh() { fun refresh() {
val request = vastApi.buildRequest( _uiState.value = _uiState.value.copy(refreshing = 2)
_refreshError.value = emptyList()
val userRequest = vastApi.buildRequest(
ApiRoute.SHOW_USER, ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user -> UserUrlRequestCallback({ newUser ->
_uiState.value = _uiState.value.copy(isRefreshing = false, user = user) _user.value = newUser
_uiState.update {
it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks
}
}, { apiFailure -> }, { apiFailure ->
_uiState.value = _uiState.value.copy(isRefreshing = false, error = apiFailure.errorMessage) _refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
}) })
) )
_uiState.value = _uiState.value.copy(isRefreshing = true) val instancesRequest = vastApi.buildRequest(
request.start() ApiRoute.GET_INSTANCES,
InstancesUrlRequestCallback({ instances ->
_rentedInstances.value = instances // TODO better way?
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
}, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update {
it.copy(refreshing = it.refreshing - 1)
}
})
)
userRequest.start()
instancesRequest.start()
// TODO I don't like this function especially the last line
} }
} }

View file

@ -9,16 +9,13 @@ 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
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
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.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.getValue import androidx.compose.runtime.getValue
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
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -32,7 +29,7 @@ import eu.m724.vastapp.activity.dashboard.DashboardViewModel
fun BillingScreen(dashboardViewModel: DashboardViewModel) { fun BillingScreen(dashboardViewModel: DashboardViewModel) {
val uiState by dashboardViewModel.uiState.collectAsState() val uiState by dashboardViewModel.uiState.collectAsState()
val user by remember(uiState) { derivedStateOf { uiState.user } } val user by dashboardViewModel.user.collectAsState()
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),

View file

@ -25,6 +25,7 @@ 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.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@ -43,13 +44,15 @@ import eu.m724.vastapp.activity.dashboard.DashboardViewModel
fun DashboardScreen(dashboardViewModel: DashboardViewModel) { fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
val uiState by dashboardViewModel.uiState.collectAsState() val uiState by dashboardViewModel.uiState.collectAsState()
val user by remember(uiState) { derivedStateOf { uiState.user } } val user by dashboardViewModel.user.collectAsState()
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState()
val remainingTime by rememberSaveable { mutableIntStateOf(6000000) } val remainingTime by rememberSaveable { mutableIntStateOf(6000000) }
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
PullToRefreshBox( PullToRefreshBox(
isRefreshing = uiState.isRefreshing, isRefreshing = isRefreshing,
state = rememberPullToRefreshState(), state = rememberPullToRefreshState(),
onRefresh = { dashboardViewModel.refresh() } onRefresh = { dashboardViewModel.refresh() }
) { ) {
@ -142,7 +145,7 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
modifier = Modifier.width(12.dp) modifier = Modifier.width(12.dp)
) )
Text( Text(
text = "4", text = rentedInstances.size.toString(),
fontSize = 22.sp fontSize = 22.sp
) )
} }

View file

@ -1,7 +1,7 @@
package eu.m724.vastapp.vastai.api package eu.m724.vastapp.vastai.api
import eu.m724.vastapp.vastai.ApiFailure import eu.m724.vastapp.vastai.ApiFailure
import eu.m724.vastapp.vastai.data.User import eu.m724.vastapp.vastai.data.Instance
import org.chromium.net.CronetException import org.chromium.net.CronetException
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo import org.chromium.net.UrlResponseInfo
@ -10,7 +10,7 @@ import java.nio.ByteBuffer
import java.nio.charset.CodingErrorAction import java.nio.charset.CodingErrorAction
class InstancesUrlRequestCallback( class InstancesUrlRequestCallback(
val onSuccess: (List<JSONObject>) -> Unit, val onSuccess: (List<Instance>) -> Unit,
val onFailure: (ApiFailure) -> Unit val onFailure: (ApiFailure) -> Unit
) : UrlRequest.Callback() { ) : UrlRequest.Callback() {
@ -40,16 +40,17 @@ class InstancesUrlRequestCallback(
} }
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) { override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
println(stringResponse) // TODO don't do that
if (info?.httpStatusCode == 200) { if (info?.httpStatusCode == 200) {
val jsonResponse = JSONObject(stringResponse.toString()) val jsonResponse = JSONObject(stringResponse.toString())
val instances = ArrayList<JSONObject>() val instances = ArrayList<Instance>()
val instancesJson = jsonResponse.getJSONArray("instances") val instancesJson = jsonResponse.getJSONArray("instances")
for (i in 0..<instancesJson.length()) { for (i in 0..<instancesJson.length()) {
instances.add(instancesJson.getJSONObject(i)) instances.add(Instance.fromJson(instancesJson.getJSONObject(i)))
} }
onSuccess(instances) // TODO make it better onSuccess(instances) // TODO handle json errors
} else { } else {
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}")) onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
println("API error: $stringResponse") println("API error: $stringResponse")

View file

@ -41,9 +41,9 @@ class UserUrlRequestCallback(
} }
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) { override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
println(stringResponse) // TODO don't do that
if (info?.httpStatusCode == 200) { if (info?.httpStatusCode == 200) {
val jsonResponse = JSONObject(stringResponse.toString()) val jsonResponse = JSONObject(stringResponse.toString())
println(jsonResponse)
onSuccess(User( onSuccess(User(
id = jsonResponse.getString("id"), id = jsonResponse.getString("id"),
username = jsonResponse.getString("username"), username = jsonResponse.getString("username"),

View file

@ -24,7 +24,7 @@ data class Instance(
* at least I think because it's recent and unavailable machines don't have that */ * at least I think because it's recent and unavailable machines don't have that */
val startDate: Long, val startDate: Long,
/** when instance will expire as unix seconds, like 1861891577 */ /** when instance will expire as unix seconds, like 1861891577 */
val endDate: Long val endDate: Long?
) { ) {
companion object { companion object {
fun fromJson(json: JSONObject): Instance { fun fromJson(json: JSONObject): Instance {
@ -36,7 +36,7 @@ data class Instance(
json.getInt("num_gpus"), json.getInt("num_gpus"),
json.getDouble("dlperf"), json.getDouble("dlperf"),
json.getDouble("start_date").toLong(), json.getDouble("start_date").toLong(),
json.getDouble("end_date").toLong() json.optLong("end_date").takeIf { it > 0 }
) )
} }
} }

View file

@ -65,7 +65,7 @@ data class Machine(
json.getString("mobo_name"), json.getString("mobo_name"),
json.getInt("cpu_ram"), json.getInt("cpu_ram"),
json.getDouble("reliability2"), json.getDouble("reliability2"),
HostingClass.entries.getOrElse(json.getInt("hostingType")) { HostingClass.PRIVATE }, HostingClass.entries.getOrElse(json.optInt("hosting_type", 0)) { HostingClass.PRIVATE },
MachineVerification.fromString(json.getString("verification")) MachineVerification.fromString(json.getString("verification"))
) )
} }