androidify
This commit is contained in:
parent
99d16d56c8
commit
c785b73c3c
21 changed files with 515 additions and 217 deletions
|
@ -20,7 +20,9 @@ android {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't forget to add another "s this is counter intuitive I know but not my fault
|
||||||
buildConfigField("String", "VASTAI_KEY", "null")
|
buildConfigField("String", "VASTAI_KEY", "null")
|
||||||
|
buildConfigField("String", "VASIAI_API_ENDPOINT", "\"https://console.vast.ai/api/v0\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -67,6 +69,7 @@ dependencies {
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
implementation(libs.androidx.runtime.livedata)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
tools:targetApi="34">
|
tools:targetApi="34">
|
||||||
<activity
|
<activity
|
||||||
android:name=".DashboardActivity"
|
android:name=".activity.dashboard.DashboardActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/title_activity_dashboard"
|
android:label="@string/title_activity_dashboard"
|
||||||
android:theme="@style/Theme.Vastapp" />
|
android:theme="@style/Theme.Vastapp" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoginActivity"
|
android:name=".activity.login.LoginActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/title_activity_login"
|
android:label="@string/title_activity_login"
|
||||||
android:theme="@style/Theme.Vastapp">
|
android:theme="@style/Theme.Vastapp">
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
package eu.m724.vastapp
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.BackEventCompat
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.activity.compose.PredictiveBackHandler
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.BottomAppBar
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.NavigationBar
|
|
||||||
import androidx.compose.material3.NavigationBarItem
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
|
||||||
import androidx.navigation.NavOptions
|
|
||||||
import androidx.navigation.NavOptionsBuilder
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import eu.m724.vastapp.dashboard.DashboardScreen
|
|
||||||
import eu.m724.vastapp.dashboard.InstancesScreen
|
|
||||||
import eu.m724.vastapp.ui.theme.VastappTheme
|
|
||||||
import eu.m724.vastapp.vastai.data.User
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
|
|
||||||
class DashboardActivity : ComponentActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val user = intent.getParcelableExtra<User>("user")!! // TODO null check
|
|
||||||
|
|
||||||
enableEdgeToEdge()
|
|
||||||
setContent {
|
|
||||||
VastappTheme {
|
|
||||||
val items = listOf(
|
|
||||||
Screen.Dashboard,
|
|
||||||
Screen.Instances,
|
|
||||||
Screen.Billing
|
|
||||||
)
|
|
||||||
|
|
||||||
val navController = rememberNavController()
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
bottomBar = {
|
|
||||||
NavigationBar {
|
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
|
||||||
val currentDestination = navBackStackEntry?.destination
|
|
||||||
|
|
||||||
items.forEach { screen ->
|
|
||||||
NavigationBarItem(
|
|
||||||
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
|
||||||
onClick = {
|
|
||||||
navController.navigate(screen.route) {
|
|
||||||
// Pop up to the start destination of the graph to
|
|
||||||
// avoid building up a large stack of destinations
|
|
||||||
// on the back stack as users select items
|
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
|
||||||
saveState = true
|
|
||||||
}
|
|
||||||
// Avoid multiple copies of the same destination when
|
|
||||||
// reselecting the same item
|
|
||||||
launchSingleTop = true
|
|
||||||
// Restore state when reselecting a previously selected item
|
|
||||||
restoreState = true
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = { Icon(screen.icon, contentDescription = stringResource(screen.resourceId)) },
|
|
||||||
label = { Text(text = stringResource(screen.resourceId)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(modifier = Modifier.padding(innerPadding)) {
|
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "dashboard") {
|
|
||||||
composable("dashboard") { DashboardScreen(user = user) }
|
|
||||||
composable("instances") { InstancesScreen() }
|
|
||||||
composable("billing") { BillingScreen(user = user) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BillingScreen(user: User) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(100.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center) {
|
|
||||||
Text("You have $%.2f".format(user.credit), fontSize = 28.sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
package eu.m724.vastapp.activity.dashboard
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
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.material3.Icon
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
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.stringResource
|
||||||
|
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
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.DashboardScreen
|
||||||
|
import eu.m724.vastapp.activity.dashboard.screen.InstancesScreen
|
||||||
|
import eu.m724.vastapp.ui.theme.VastappTheme
|
||||||
|
import eu.m724.vastapp.vastai.VastApi
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
import org.chromium.net.CronetEngine
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class DashboardActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val user = intent.getParcelableExtra<User>("user")!! // TODO null check
|
||||||
|
|
||||||
|
val executor = Executors.newSingleThreadExecutor()
|
||||||
|
val cronetEngine = CronetEngine.Builder(baseContext).build()
|
||||||
|
val vastApi = VastApi(user.apiKey, cronetEngine, executor) // TODO use that from login activity
|
||||||
|
|
||||||
|
val dashboardViewModel = DashboardViewModel(user, vastApi)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
VastappTheme {
|
||||||
|
val items = listOf(
|
||||||
|
Screen.Dashboard,
|
||||||
|
Screen.Instances,
|
||||||
|
Screen.Billing
|
||||||
|
)
|
||||||
|
|
||||||
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
bottomBar = {
|
||||||
|
MyNavigationBar(items, navController = navController)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
Column(modifier = Modifier.padding(innerPadding)) {
|
||||||
|
NavHost(navController = navController, startDestination = "dashboard") {
|
||||||
|
composable("dashboard") { DashboardScreen(dashboardViewModel) }
|
||||||
|
composable("instances") { InstancesScreen(dashboardViewModel) }
|
||||||
|
composable("billing") { BillingScreen(dashboardViewModel) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MyNavigationBar(items: List<Screen>, navController: NavHostController) {
|
||||||
|
NavigationBar {
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentDestination = navBackStackEntry?.destination
|
||||||
|
|
||||||
|
items.forEach { screen ->
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(screen.route) {
|
||||||
|
// Pop up to the start destination of the graph to
|
||||||
|
// avoid building up a large stack of destinations
|
||||||
|
// on the back stack as users select items
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
// Avoid multiple copies of the same destination when
|
||||||
|
// reselecting the same item
|
||||||
|
launchSingleTop = true
|
||||||
|
// Restore state when reselecting a previously selected item
|
||||||
|
restoreState = true
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
screen.icon,
|
||||||
|
contentDescription = stringResource(screen.resourceId)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { Text(text = stringResource(screen.resourceId)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.m724.vastapp.activity.dashboard
|
||||||
|
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
|
||||||
|
data class DashboardUiState(
|
||||||
|
val isRefreshing: Boolean,
|
||||||
|
val user: User,
|
||||||
|
val error: String?
|
||||||
|
) { }
|
|
@ -0,0 +1,45 @@
|
||||||
|
package eu.m724.vastapp.activity.dashboard
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import eu.m724.vastapp.activity.login.LoginUiState
|
||||||
|
import eu.m724.vastapp.vastai.ApiRoute
|
||||||
|
import eu.m724.vastapp.vastai.VastApi
|
||||||
|
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DashboardViewModel(private val _user: User, val vastApi: VastApi) : ViewModel() {
|
||||||
|
private val _uiState: MutableStateFlow<DashboardUiState> =
|
||||||
|
MutableStateFlow(DashboardUiState(false, _user, null))
|
||||||
|
val uiState: StateFlow<DashboardUiState> =
|
||||||
|
_uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _navigationEvent = MutableSharedFlow<String>()
|
||||||
|
val navigationEvent: SharedFlow<String> = _navigationEvent.asSharedFlow()
|
||||||
|
|
||||||
|
fun navigateTo(route: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_navigationEvent.emit(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshData() {
|
||||||
|
val request = vastApi.buildRequest(
|
||||||
|
ApiRoute.SHOW_USER,
|
||||||
|
UserUrlRequestCallback({ user ->
|
||||||
|
_uiState.value = _uiState.value.copy(isRefreshing = false, user = user)
|
||||||
|
}, { apiFailure ->
|
||||||
|
_uiState.value = _uiState.value.copy(isRefreshing = false, error = apiFailure.errorMessage)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package eu.m724.vastapp.activity.dashboard.screen
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import eu.m724.vastapp.activity.dashboard.DashboardViewModel
|
||||||
|
|
||||||
|
class Billing {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BillingScreen(dashboardViewModel: DashboardViewModel) {
|
||||||
|
|
||||||
|
}
|
|
@ -1,20 +1,14 @@
|
||||||
package eu.m724.vastapp.dashboard
|
package eu.m724.vastapp.activity.dashboard.screen
|
||||||
|
|
||||||
import android.widget.Space
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
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.fillMaxSize
|
|
||||||
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.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Info
|
|
||||||
import androidx.compose.material.icons.filled.ShoppingCart
|
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardColors
|
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -28,20 +22,20 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.modifier.modifierLocalMapOf
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.FontScaling
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.m724.vastapp.R
|
import eu.m724.vastapp.R
|
||||||
|
import eu.m724.vastapp.activity.dashboard.DashboardViewModel
|
||||||
import eu.m724.vastapp.vastai.data.User
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
class Dashboard {
|
class Dashboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(user: User) {
|
fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
|
||||||
var balance by rememberSaveable { mutableDoubleStateOf(user.credit) }
|
var balance by rememberSaveable { mutableDoubleStateOf(user.credit) }
|
||||||
var remainingTime by rememberSaveable { mutableIntStateOf(0) }
|
var remainingTime by rememberSaveable { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
@ -114,6 +108,70 @@ fun DashboardScreen(user: User) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.width(340.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package eu.m724.vastapp.activity.dashboard.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.m724.vastapp.activity.dashboard.DashboardViewModel
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class Instances {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstancesScreen(dashboardViewModel: DashboardViewModel) {
|
||||||
|
Column {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstanceCard(instance: JSONObject, modifier: Modifier = Modifier) {
|
||||||
|
Card(modifier = modifier) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = instance.getString("id"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewInstanceCard() {
|
||||||
|
val instance = JSONObject()
|
||||||
|
instance.put("id", 3423941)
|
||||||
|
InstanceCard(instance = instance, modifier = Modifier.size(300.dp))
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
package eu.m724.vastapp
|
package eu.m724.vastapp.activity.dashboard.screen
|
||||||
|
|
||||||
import android.graphics.drawable.Icon
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
|
||||||
import androidx.compose.material.icons.outlined.Home
|
import androidx.compose.material.icons.outlined.Home
|
||||||
import androidx.compose.material.icons.outlined.Menu
|
import androidx.compose.material.icons.outlined.Menu
|
||||||
import androidx.compose.material.icons.outlined.ShoppingCart
|
import androidx.compose.material.icons.outlined.ShoppingCart
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import eu.m724.vastapp.R
|
||||||
|
|
||||||
sealed class Screen(val route: String, @StringRes val resourceId: Int, val icon: ImageVector) {
|
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 Dashboard : Screen("dashboard", R.string.nav_dashboard, Icons.Outlined.Home)
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.m724.vastapp
|
package eu.m724.vastapp.activity.login
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
@ -37,9 +36,13 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
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.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
@ -50,20 +53,17 @@ import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import eu.m724.vastapp.BuildConfig
|
||||||
|
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.api.ApiFailure
|
|
||||||
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
|
|
||||||
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 org.chromium.net.CronetEngine
|
||||||
import org.chromium.net.UrlRequest
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class LoginActivity : ComponentActivity() {
|
class LoginActivity : ComponentActivity() {
|
||||||
private val executor: Executor = Executors.newSingleThreadExecutor()
|
|
||||||
private lateinit var cronetEngine: CronetEngine
|
|
||||||
private lateinit var dashboardLauncher: ActivityResultLauncher<Intent>
|
private lateinit var dashboardLauncher: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -73,9 +73,23 @@ class LoginActivity : ComponentActivity() {
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result -> finish() } // TODO re-login here
|
) { result -> finish() } // TODO re-login here
|
||||||
|
|
||||||
cronetEngine = CronetEngine.Builder(applicationContext).build()
|
val executor = Executors.newSingleThreadExecutor()
|
||||||
val tryLogin: (String, (ApiFailure) -> Unit) -> Unit =
|
val cronetEngine = CronetEngine.Builder(baseContext).build()
|
||||||
{ apiKey, onFailure -> tryLogin(apiKey, { user -> loadApp(user) }, onFailure) }
|
val loginViewModel = LoginViewModel(cronetEngine, executor)
|
||||||
|
|
||||||
|
loginViewModel.error.observe(this) { errorMessage ->
|
||||||
|
if (errorMessage != null) {
|
||||||
|
Toast.makeText(baseContext, errorMessage, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecyclescope
|
||||||
|
loginViewModel.uiState.collect { state ->
|
||||||
|
if (state is LoginUiState.Success) {
|
||||||
|
loadApp(state.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
|
@ -89,7 +103,7 @@ class LoginActivity : ComponentActivity() {
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
LoginApp(applicationContext, tryLogin)
|
LoginApp(loginViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,25 +119,16 @@ class LoginActivity : ComponentActivity() {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryLogin(apiKey: String, onSuccess: (User) -> Unit, onFailure: (ApiFailure) -> Unit) {
|
|
||||||
val requestBuilder = cronetEngine.newUrlRequestBuilder(
|
|
||||||
"https://console.vast.ai/api/v0/users/current",
|
|
||||||
UserUrlRequestCallback(onSuccess, onFailure),
|
|
||||||
executor
|
|
||||||
).addHeader("Authorization", "Bearer $apiKey")
|
|
||||||
|
|
||||||
val request: UrlRequest = requestBuilder.build()
|
|
||||||
request.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginApp(context: Context, tryLogin: (String, (ApiFailure) -> Unit) -> Unit) {
|
fun LoginApp(loginViewModel: LoginViewModel) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val uiState by loginViewModel.uiState.collectAsState()
|
||||||
|
val loginErrorMessage by loginViewModel.error.observeAsState() // TODO put this in uistate
|
||||||
|
val isIdle by remember(uiState) { derivedStateOf { uiState !is LoginUiState.Loading } }
|
||||||
|
|
||||||
var apiKey by rememberSaveable { mutableStateOf(BuildConfig.VASTAI_KEY ?: "") }
|
var apiKey by rememberSaveable { mutableStateOf(BuildConfig.VASTAI_KEY ?: "") }
|
||||||
var loading by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var advancedOpen by rememberSaveable { mutableStateOf(false) }
|
var advancedOpen by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val transition = updateTransition(targetState = advancedOpen, label = "Advanced Menu Transition")
|
val transition = updateTransition(targetState = advancedOpen, label = "Advanced Menu Transition")
|
||||||
|
@ -131,15 +136,6 @@ fun LoginApp(context: Context, tryLogin: (String, (ApiFailure) -> Unit) -> Unit)
|
||||||
if (state) 180f else 0f
|
if (state) 180f else 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
val login: () -> Unit = {
|
|
||||||
tryLogin(apiKey) { apiFailure ->
|
|
||||||
loading = false
|
|
||||||
coroutineScope.launch {
|
|
||||||
Toast.makeText(context, apiFailure.errorMessage, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.width(300.dp),
|
modifier = Modifier.width(300.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
|
@ -147,18 +143,19 @@ fun LoginApp(context: Context, tryLogin: (String, (ApiFailure) -> Unit) -> Unit)
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !loading,
|
enabled = isIdle,
|
||||||
value = apiKey,
|
value = apiKey,
|
||||||
onValueChange = { apiKey = it },
|
onValueChange = { apiKey = it },
|
||||||
label = { Text(text = "API key") },
|
label = { Text(text = "API key") },
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
singleLine = true
|
singleLine = true,
|
||||||
|
isError = loginErrorMessage != null
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
Row {
|
Row {
|
||||||
TextButton(
|
TextButton(
|
||||||
enabled = !loading,
|
enabled = isIdle,
|
||||||
onClick = {
|
onClick = {
|
||||||
advancedOpen = !advancedOpen
|
advancedOpen = !advancedOpen
|
||||||
}
|
}
|
||||||
|
@ -172,13 +169,12 @@ fun LoginApp(context: Context, tryLogin: (String, (ApiFailure) -> Unit) -> Unit)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Button(
|
Button(
|
||||||
enabled = !loading,
|
enabled = isIdle,
|
||||||
onClick = {
|
onClick = {
|
||||||
loading = true
|
loginViewModel.tryLogin(apiKey)
|
||||||
login()
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (loading) {
|
if (uiState is LoginUiState.Loading) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Text("Log in")
|
Text("Log in")
|
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.m724.vastapp.activity.login
|
||||||
|
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
|
||||||
|
sealed interface LoginUiState {
|
||||||
|
/**
|
||||||
|
* when nothing is being done
|
||||||
|
* you should get the error
|
||||||
|
*/
|
||||||
|
data object Idle : LoginUiState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when we're currently logging in
|
||||||
|
*/
|
||||||
|
data object Loading : LoginUiState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when logged in
|
||||||
|
* @param user the logged in user
|
||||||
|
*/
|
||||||
|
data class Success(val user: User) : LoginUiState
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package eu.m724.vastapp.activity.login
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import eu.m724.vastapp.vastai.ApiFailure
|
||||||
|
import eu.m724.vastapp.vastai.ApiRoute
|
||||||
|
import eu.m724.vastapp.vastai.VastApi
|
||||||
|
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.chromium.net.CronetEngine
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
class LoginViewModel(val cronetEngine: CronetEngine, val executor: Executor) : ViewModel() {
|
||||||
|
private val _uiState: MutableStateFlow<LoginUiState> =
|
||||||
|
MutableStateFlow(LoginUiState.Idle)
|
||||||
|
val uiState: StateFlow<LoginUiState> =
|
||||||
|
_uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _error = MutableLiveData<String?>(null)
|
||||||
|
val error: LiveData<String?> = _error // TODO put this in uistate
|
||||||
|
|
||||||
|
fun tryLogin(apiKey: String) {
|
||||||
|
val vastApi = VastApi(apiKey, cronetEngine, executor)
|
||||||
|
val request = vastApi.buildRequest(
|
||||||
|
ApiRoute.SHOW_USER,
|
||||||
|
UserUrlRequestCallback({ user ->
|
||||||
|
_uiState.value = LoginUiState.Success(user)
|
||||||
|
}, { apiFailure ->
|
||||||
|
_uiState.value = LoginUiState.Idle
|
||||||
|
_error.value = apiFailure.errorMessage
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
_uiState.value = LoginUiState.Loading
|
||||||
|
request.start()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package eu.m724.vastapp.dashboard
|
|
||||||
|
|
||||||
class Billing {
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package eu.m724.vastapp.dashboard
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import eu.m724.vastapp.vastai.data.User
|
|
||||||
|
|
||||||
class Instances {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InstancesScreen() {
|
|
||||||
Column {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Instance() {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
package eu.m724.vastapp.vastai.api
|
package eu.m724.vastapp.vastai
|
||||||
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
|
||||||
|
|
||||||
data class ApiFailure(
|
data class ApiFailure(
|
||||||
/** user friendly error message */
|
/** user friendly error message */
|
6
app/src/main/java/eu/m724/vastapp/vastai/ApiRoute.kt
Normal file
6
app/src/main/java/eu/m724/vastapp/vastai/ApiRoute.kt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package eu.m724.vastapp.vastai
|
||||||
|
|
||||||
|
enum class ApiRoute(val path: String, val method: String) {
|
||||||
|
SHOW_USER("/users/current", "GET"),
|
||||||
|
GET_INSTANCES("/instances", "GET")
|
||||||
|
}
|
44
app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt
Normal file
44
app/src/main/java/eu/m724/vastapp/vastai/VastApi.kt
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package eu.m724.vastapp.vastai
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import eu.m724.vastapp.BuildConfig
|
||||||
|
import org.chromium.net.CronetEngine
|
||||||
|
import org.chromium.net.UrlRequest
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
class VastApi(
|
||||||
|
val apiKey: String,
|
||||||
|
val cronetEngine: CronetEngine,
|
||||||
|
val executor: Executor
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* build an api request
|
||||||
|
* don't forget to call .start() on the returned [UrlRequest]
|
||||||
|
*
|
||||||
|
* @param endpoint the endpoint path starting with a slash like /users/current
|
||||||
|
* @param callback any callback for example [UserUrlRequestCallback]
|
||||||
|
* @return an [UrlRequest] you must .start() yourself
|
||||||
|
*/
|
||||||
|
fun buildRequest(endpoint: String, callback: UrlRequest.Callback): UrlRequest {
|
||||||
|
val requestBuilder = cronetEngine.newUrlRequestBuilder(
|
||||||
|
BuildConfig.VASIAI_API_ENDPOINT + endpoint,
|
||||||
|
callback,
|
||||||
|
executor
|
||||||
|
).addHeader("Authorization", "Bearer $apiKey")
|
||||||
|
|
||||||
|
return requestBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build an api request
|
||||||
|
* don't forget to call .start() on the returned [UrlRequest]
|
||||||
|
*
|
||||||
|
* @param apiRoute the api route
|
||||||
|
* @param callback any callback for example [UserUrlRequestCallback]
|
||||||
|
* @return an [UrlRequest] you must .start() yourself
|
||||||
|
*/
|
||||||
|
fun buildRequest(apiRoute: ApiRoute, callback: UrlRequest.Callback): UrlRequest {
|
||||||
|
return buildRequest(apiRoute.path, callback)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package eu.m724.vastapp.vastai.api
|
||||||
|
|
||||||
|
import eu.m724.vastapp.vastai.ApiFailure
|
||||||
|
import eu.m724.vastapp.vastai.data.User
|
||||||
|
import org.chromium.net.CronetException
|
||||||
|
import org.chromium.net.UrlRequest
|
||||||
|
import org.chromium.net.UrlResponseInfo
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.CodingErrorAction
|
||||||
|
|
||||||
|
class InstancesUrlRequestCallback(
|
||||||
|
val onSuccess: (List<JSONObject>) -> Unit,
|
||||||
|
val onFailure: (ApiFailure) -> Unit
|
||||||
|
) : UrlRequest.Callback() {
|
||||||
|
|
||||||
|
private val stringResponse = StringBuilder()
|
||||||
|
|
||||||
|
override fun onRedirectReceived(
|
||||||
|
request: UrlRequest?,
|
||||||
|
info: UrlResponseInfo?,
|
||||||
|
newLocationUrl: String?
|
||||||
|
) {
|
||||||
|
request?.followRedirect()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
|
request?.read(ByteBuffer.allocateDirect(102400))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReadCompleted(
|
||||||
|
request: UrlRequest?,
|
||||||
|
info: UrlResponseInfo?,
|
||||||
|
byteBuffer: ByteBuffer?
|
||||||
|
) {
|
||||||
|
byteBuffer?.clear()
|
||||||
|
request?.read(byteBuffer)
|
||||||
|
|
||||||
|
stringResponse.append(Charsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.IGNORE).decode(byteBuffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
|
if (info?.httpStatusCode == 200) {
|
||||||
|
val jsonResponse = JSONObject(stringResponse.toString())
|
||||||
|
val instances = ArrayList<JSONObject>()
|
||||||
|
|
||||||
|
val instancesJson = jsonResponse.getJSONArray("instances")
|
||||||
|
for (i in 0..<instancesJson.length()) {
|
||||||
|
instances.add(instancesJson.getJSONObject(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(instances) // TODO make it better
|
||||||
|
} else {
|
||||||
|
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
|
||||||
|
println("API error: $stringResponse")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
|
||||||
|
onFailure(ApiFailure("Network error: ${error?.message ?: "Unknown"}"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
package eu.m724.vastapp.vastai.api
|
package eu.m724.vastapp.vastai.api
|
||||||
|
|
||||||
|
import eu.m724.vastapp.vastai.ApiFailure
|
||||||
import eu.m724.vastapp.vastai.data.User
|
import eu.m724.vastapp.vastai.data.User
|
||||||
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
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.Channels
|
|
||||||
import java.nio.channels.WritableByteChannel
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.nio.charset.CodingErrorAction
|
import java.nio.charset.CodingErrorAction
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ composeBom = "2024.06.00"
|
||||||
playServicesCronet = "18.1.0"
|
playServicesCronet = "18.1.0"
|
||||||
navigationCompose = "2.7.7"
|
navigationCompose = "2.7.7"
|
||||||
secretsGradlePlugin = "2.0.1"
|
secretsGradlePlugin = "2.0.1"
|
||||||
|
runtimeLivedata = "1.6.8"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
@ -33,6 +34,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
play-services-cronet = { module = "com.google.android.gms:play-services-cronet", version.ref = "playServicesCronet" }
|
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-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
Loading…
Reference in a new issue