super ultra mega refactoring

- now extending Application
- using that Application to store data like user or instances
- completed loading screen
  * made loading seamless
- and made other stuff work with that
This commit is contained in:
Minecon724 2024-08-05 14:11:53 +02:00
parent 9574ed496d
commit ae4912fd29
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
19 changed files with 395 additions and 106 deletions

View file

@ -14,7 +14,19 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Vastapp" android:theme="@style/Theme.Vastapp"
android:name=".VastApplication"
tools:targetApi="34"> tools:targetApi="34">
<activity
android:name=".activity.dashboard.loading.LoadingActivity"
android:exported="true"
android:label="@string/title_activity_loading"
android:theme="@style/Theme.Vastapp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity <activity
android:name=".activity.termux.TermuxSshActivity" android:name=".activity.termux.TermuxSshActivity"
android:exported="false" android:exported="false"
@ -30,11 +42,6 @@
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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> </activity>
</application> </application>

View file

@ -0,0 +1,38 @@
package eu.m724.vastapp
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import eu.m724.vastapp.vastai.Account
import eu.m724.vastapp.vastai.VastApi
import org.chromium.net.CronetEngine
import java.util.concurrent.Executors
class VastApplication : Application() {
private lateinit var cronetEngine: CronetEngine
lateinit var vastApi: VastApi // TODO maybe make private?
private lateinit var loginSharedPreferences: SharedPreferences
var account: Account? = null
override fun onCreate() {
super.onCreate()
cronetEngine = CronetEngine.Builder(baseContext)
.enableBrotli(true)
.build() // http3 is not supported on cloud.vast.ai
vastApi = VastApi("", cronetEngine, Executors.newSingleThreadExecutor())
loginSharedPreferences = applicationContext.getSharedPreferences("login", Context.MODE_PRIVATE)
}
fun submitKey(apiKey: String) {
with (loginSharedPreferences.edit()) {
putString("apiKey", apiKey) // TODO encrypt
apply()
}
}
fun loadKey(): String? {
return loginSharedPreferences.getString("apiKey", null)
}
}

View file

@ -36,26 +36,18 @@ 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.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.data.User
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine
import java.util.concurrent.Executors
class DashboardActivity : ComponentActivity() { class DashboardActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val user = intent.getParcelableExtra<User>("user")!! // TODO null check val dashboardViewModel = DashboardViewModel(application)
if (intent.getBooleanExtra("direct", false).not()) {
val executor = Executors.newSingleThreadExecutor() dashboardViewModel.refresh(this)
val cronetEngine = CronetEngine.Builder(baseContext).enableBrotli(true).build() }
val vastApi = VastApi(user.apiKey, cronetEngine, executor) // TODO use that from login activity
val dashboardViewModel = DashboardViewModel(user, vastApi)
lifecycleScope.launch { lifecycleScope.launch {
dashboardViewModel.refresh(this@DashboardActivity)
repeatOnLifecycle(Lifecycle.State.STARTED) { repeatOnLifecycle(Lifecycle.State.STARTED) {
dashboardViewModel.refreshError.collect { dashboardViewModel.refreshError.collect {
it.forEach { errorMsg -> it.forEach { errorMsg ->

View file

@ -1,19 +1,19 @@
package eu.m724.vastapp.activity.dashboard package eu.m724.vastapp.activity.dashboard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application
import android.content.Context import android.content.Context
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import eu.m724.vastapp.R import eu.m724.vastapp.R
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.activity.Opener import eu.m724.vastapp.activity.Opener
import eu.m724.vastapp.activity.PermissionChecker import eu.m724.vastapp.activity.PermissionChecker
import eu.m724.vastapp.vastai.ApiRoute import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback 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.RentedInstance import eu.m724.vastapp.vastai.data.RentedInstance
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
@ -21,30 +21,24 @@ import kotlinx.coroutines.flow.update
class DashboardViewModel( class DashboardViewModel(
initialUser: User, application: Application
private val vastApi: VastApi ) : AndroidViewModel(application) { // TODO do something with the user
) : ViewModel() { // TODO do something with the user
private val _uiState: MutableStateFlow<DashboardUiState> = private val _uiState: MutableStateFlow<DashboardUiState> =
MutableStateFlow(DashboardUiState(0)) MutableStateFlow(DashboardUiState(0))
val uiState: StateFlow<DashboardUiState> = val uiState: StateFlow<DashboardUiState> =
_uiState.asStateFlow() _uiState.asStateFlow()
private val _rentedInstances: MutableStateFlow<List<RentedInstance>> = MutableStateFlow(emptyList())
val rentedInstances: StateFlow<List<RentedInstance>> = _rentedInstances.asStateFlow()
private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser)
val user: StateFlow<User> = _user.asStateFlow()
private val _remainingTime: MutableStateFlow<Int> = MutableStateFlow(-1)
var remainingTime: StateFlow<Int> = _remainingTime.asStateFlow()
private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList()) private val _refreshError: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow() val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
private val _termuxAvailable: MutableStateFlow<Int> = MutableStateFlow(0) private val _termuxAvailable: MutableStateFlow<Int> = MutableStateFlow(0)
val termuxAvailable: StateFlow<Int> = _termuxAvailable.asStateFlow() val termuxAvailable: StateFlow<Int> = _termuxAvailable.asStateFlow()
private val application = getApplication<VastApplication>()
private val vastApi = this.application.vastApi
val account = this.application.account!!
fun refresh(activity: ComponentActivity) { fun refresh(activity: ComponentActivity) {
_uiState.value = _uiState.value.copy(refreshing = 2) _uiState.value = _uiState.value.copy(refreshing = 2)
_refreshError.value = emptyList() _refreshError.value = emptyList()
@ -52,7 +46,7 @@ class DashboardViewModel(
val userRequest = vastApi.buildRequest( val userRequest = vastApi.buildRequest(
ApiRoute.SHOW_USER, ApiRoute.SHOW_USER,
UserUrlRequestCallback({ newUser -> UserUrlRequestCallback({ newUser ->
_user.value = newUser account.updateUser(newUser)
_uiState.update { _uiState.update {
it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks
} }
@ -67,33 +61,17 @@ class DashboardViewModel(
val instancesRequest = vastApi.buildRequest( val instancesRequest = vastApi.buildRequest(
ApiRoute.GET_INSTANCES, ApiRoute.GET_INSTANCES,
InstancesUrlRequestCallback({ instances -> InstancesUrlRequestCallback({ instances ->
_rentedInstances.value = instances // TODO better way? account.updateRentedInstances(instances)
_uiState.update { _uiState.update {
it.copy(refreshing = it.refreshing - 1) it.copy(refreshing = it.refreshing - 1)
} }
if (instances.isEmpty()) { // TODO move this
_remainingTime.value = -1
} else {
var totalDph = 0.0
instances.forEach {
if (it.status == "running")
totalDph += it.instance.pricing.dphTotal!!
else
totalDph += it.instance.pricing.dphTotal!! - it.instance.pricing.dphBase
// TODO make this ideal
}
_remainingTime.value = (user.value.credit / totalDph * 3600).toInt()
}
}, { apiFailure -> }, { apiFailure ->
_refreshError.update { it + apiFailure.errorMessage!! } _refreshError.update { it + apiFailure.errorMessage!! }
_uiState.update { _uiState.update {
it.copy(refreshing = it.refreshing - 1) it.copy(refreshing = it.refreshing - 1)
} }
}) })
) ) // TODO move all that refreshing to some shared place
userRequest.start() userRequest.start()
instancesRequest.start() instancesRequest.start()
@ -111,7 +89,7 @@ class DashboardViewModel(
} }
} else -1 // not available } else -1 // not available
// TODO I don't like this function especially the last line // TODO I don't like this function especially the last line. I think it should be moved to application
} }
@SuppressLint("SdCardPath") @SuppressLint("SdCardPath")
@ -123,7 +101,7 @@ class DashboardViewModel(
PermissionChecker.requestIfNoPermission( PermissionChecker.requestIfNoPermission(
"com.termux.permission.RUN_COMMAND", "com.termux.permission.RUN_COMMAND",
activity, 0 activity, 0
) { granted, asked -> ) { granted, _ ->
if (granted) { if (granted) {
val arguments = arrayOf( val arguments = arrayOf(
"/data/data/com.termux/files/usr/bin/ssh", "/data/data/com.termux/files/usr/bin/ssh",

View file

@ -0,0 +1,76 @@
package eu.m724.vastapp.activity.dashboard.loading
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import eu.m724.vastapp.R
import eu.m724.vastapp.activity.dashboard.DashboardActivity
import eu.m724.vastapp.activity.dashboard.loading.ui.theme.VastappTheme
import eu.m724.vastapp.activity.login.LoginActivity
class LoadingActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = LoadingViewModel(application) { result ->
println(result.loggedIn)
val intent =
if (result.loggedIn) {
Intent(this, DashboardActivity::class.java)
.putExtra("direct", true)
} else {
Intent(this, LoginActivity::class.java)
.putExtra("error", result.error!!)
}
this.startActivity(intent)
finish()
}
viewModel.init()
enableEdgeToEdge()
setContent {
VastappTheme {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.fillMaxHeight().weight(1f))
Box(
modifier = Modifier
.fillMaxHeight()
.weight(1f),
contentAlignment = Alignment.Center
) {
Icon(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "app icon"
)
}
Box(
modifier = Modifier
.fillMaxHeight()
.weight(1f),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
}
}
}

View file

@ -0,0 +1,6 @@
package eu.m724.vastapp.activity.dashboard.loading
data class LoadingResult(
val loggedIn: Boolean,
val error: String?
)

View file

@ -0,0 +1,54 @@
package eu.m724.vastapp.activity.dashboard.loading
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.vastai.Account
import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.api.InstancesUrlRequestCallback
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
class LoadingViewModel(
application: Application,
private val onEnded: (LoadingResult) -> Unit,
) : AndroidViewModel(application) {
private val application = application as VastApplication
fun init() {
val vastApi = application.vastApi
val apiKey = application.loadKey()
if (apiKey != null) {
vastApi.apiKey = apiKey
val request = vastApi.buildRequest(
ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user ->
application.account = Account(user)
loadInstances()
}, { apiFailure ->
onEnded(LoadingResult(false, apiFailure.errorMessage))
})
)
request.start()
}
}
private fun loadInstances() {
val vastApi = application.vastApi
val instancesRequest = vastApi.buildRequest(
ApiRoute.GET_INSTANCES,
InstancesUrlRequestCallback({ instances ->
application.account!!.updateRentedInstances(instances)
onEnded(LoadingResult(true, null))
}, { apiFailure ->
// TODO I don't know what to do yet
onEnded(LoadingResult(true, null))
})
)
instancesRequest.start()
}
}

View file

@ -0,0 +1,11 @@
package eu.m724.vastapp.activity.dashboard.loading.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View file

@ -0,0 +1,58 @@
package eu.m724.vastapp.activity.dashboard.loading.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun VastappTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View file

@ -0,0 +1,34 @@
package eu.m724.vastapp.activity.dashboard.loading.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View file

@ -29,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 dashboardViewModel.user.collectAsState() val user by dashboardViewModel.account.user.collectAsState()
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),

View file

@ -44,9 +44,9 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
val context = LocalContext.current val context = LocalContext.current
val uiState by dashboardViewModel.uiState.collectAsState() val uiState by dashboardViewModel.uiState.collectAsState()
val user by dashboardViewModel.user.collectAsState() val user by dashboardViewModel.account.user.collectAsState()
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState() val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState()
val remainingTime by dashboardViewModel.remainingTime.collectAsState() val remainingTime by dashboardViewModel.account.remainingTime.collectAsState()
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } } val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()

View file

@ -42,7 +42,7 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) {
val activity = LocalContext.current as ComponentActivity val activity = LocalContext.current as ComponentActivity
val uiState by dashboardViewModel.uiState.collectAsState() val uiState by dashboardViewModel.uiState.collectAsState()
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState() val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState()
val termuxAvailable by dashboardViewModel.termuxAvailable.collectAsState() val termuxAvailable by dashboardViewModel.termuxAvailable.collectAsState()
// TODO actually get instances // TODO actually get instances

View file

@ -55,7 +55,6 @@ 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 androidx.lifecycle.lifecycleScope
import eu.m724.vastapp.BuildConfig
import eu.m724.vastapp.R import eu.m724.vastapp.R
import eu.m724.vastapp.activity.dashboard.DashboardActivity import eu.m724.vastapp.activity.dashboard.DashboardActivity
import eu.m724.vastapp.ui.theme.VastappTheme import eu.m724.vastapp.ui.theme.VastappTheme
@ -84,6 +83,11 @@ class LoginActivity : ComponentActivity() {
} }
} }
val loadingError = intent.getStringExtra("error")
if (loadingError != null) {
Toast.makeText(baseContext, loadingError, Toast.LENGTH_SHORT).show()
}
lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope lifecycleScope.launch { // TODO I was suggested not to launch an activity from a lifecycle scope
loginViewModel.uiState.collect { state -> loginViewModel.uiState.collect { state ->
if (state is LoginUiState.Success) { if (state is LoginUiState.Success) {
@ -92,9 +96,6 @@ class LoginActivity : ComponentActivity() {
} }
} }
if (BuildConfig.AUTO_LOGIN)
loginViewModel.loadKey()
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
VastappTheme { VastappTheme {

View file

@ -1,12 +1,11 @@
package eu.m724.vastapp.activity.login package eu.m724.vastapp.activity.login
import android.app.Application import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import eu.m724.vastapp.VastApplication
import eu.m724.vastapp.vastai.ApiRoute import eu.m724.vastapp.vastai.ApiRoute
import eu.m724.vastapp.vastai.VastApi
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -34,35 +33,16 @@ class LoginViewModel(
private val _fullscreenLoading = MutableStateFlow<Boolean>(false) private val _fullscreenLoading = MutableStateFlow<Boolean>(false)
var fullscreenLoading: StateFlow<Boolean> = _fullscreenLoading.asStateFlow() var fullscreenLoading: StateFlow<Boolean> = _fullscreenLoading.asStateFlow()
private val applicationContext = getApplication<Application>().applicationContext private val application = getApplication<VastApplication>()
private val sharedPreferences = applicationContext.getSharedPreferences("login", Context.MODE_PRIVATE)
private fun saveKey() {
with (sharedPreferences.edit()) {
putString("apiKey", apiKey.value) // TODO encrypt
apply()
}
}
fun loadKey() {
val apiKey = sharedPreferences.getString("apiKey", null)
if (apiKey != null) {
_apiKey.value = apiKey
_fullscreenLoading.value = true
tryLogin()
}
}
fun tryLogin() { fun tryLogin() {
val apiKey = apiKey.value val vastApi = application.vastApi
vastApi.apiKey = apiKey.value
val vastApi = VastApi(apiKey, cronetEngine, executor)
val request = vastApi.buildRequest( val request = vastApi.buildRequest(
ApiRoute.SHOW_USER, ApiRoute.SHOW_USER,
UserUrlRequestCallback({ user -> UserUrlRequestCallback({ user ->
saveKey() // TODO toggle for this application.submitKey(apiKey.value) // TODO toggle for this
_uiState.value = LoginUiState.Success(user) _uiState.value = LoginUiState.Success(user)
}, { apiFailure -> }, { apiFailure ->
_uiState.value = LoginUiState.Idle _uiState.value = LoginUiState.Idle

View file

@ -0,0 +1,49 @@
package eu.m724.vastapp.vastai
import eu.m724.vastapp.vastai.data.RentedInstance
import eu.m724.vastapp.vastai.data.User
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class Account(
initialUser: User
) {
private val _rentedInstances: MutableStateFlow<List<RentedInstance>> = MutableStateFlow(emptyList())
val rentedInstances: StateFlow<List<RentedInstance>> = _rentedInstances.asStateFlow()
private val _user: MutableStateFlow<User> = MutableStateFlow(initialUser)
val user: StateFlow<User> = _user.asStateFlow()
private val _remainingTime: MutableStateFlow<Int> = MutableStateFlow(-1)
var remainingTime: StateFlow<Int> = _remainingTime.asStateFlow()
fun updateUser(user: User) {
_user.value = user
}
fun updateRentedInstances(rentedInstances: List<RentedInstance>) { // TODO better way?
_rentedInstances.value = rentedInstances
calculateRemainingTime()
}
fun calculateRemainingTime() {
val rentedInstances = rentedInstances.value
if (rentedInstances.isEmpty()) {
_remainingTime.value = -1
} else {
var totalDph = 0.0
rentedInstances.forEach {
if (it.status == "running")
totalDph += it.instance.pricing.dphTotal!!
else
totalDph += it.instance.pricing.dphTotal!! - it.instance.pricing.dphBase
// TODO make this ideal
}
_remainingTime.value = (user.value.credit / totalDph * 3600).toInt()
}
}
}

View file

@ -1,16 +1,14 @@
package eu.m724.vastapp.vastai package eu.m724.vastapp.vastai
import android.os.Parcel
import android.os.Parcelable
import eu.m724.vastapp.BuildConfig import eu.m724.vastapp.BuildConfig
import org.chromium.net.CronetEngine import org.chromium.net.CronetEngine
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import java.util.concurrent.Executor import java.util.concurrent.Executor
class VastApi( class VastApi(
val apiKey: String, var apiKey: String, // TODO make private?
val cronetEngine: CronetEngine, private val cronetEngine: CronetEngine,
val executor: Executor private val executor: Executor
) { ) {
/** /**
* build an api request * build an api request

View file

@ -43,8 +43,10 @@ class UserUrlRequestCallback(
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) { override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
println(stringResponse) // TODO don't do that println(stringResponse) // TODO don't do that
if (info?.httpStatusCode == 200) { if (info?.httpStatusCode == 200) {
try {
val jsonResponse = JSONObject(stringResponse.toString()) val jsonResponse = JSONObject(stringResponse.toString())
onSuccess(User( onSuccess(
User(
id = jsonResponse.getString("id"), id = jsonResponse.getString("id"),
username = jsonResponse.getString("username"), username = jsonResponse.getString("username"),
email = jsonResponse.getString("email"), email = jsonResponse.getString("email"),
@ -52,7 +54,12 @@ class UserUrlRequestCallback(
credit = jsonResponse.getDouble("credit"), credit = jsonResponse.getDouble("credit"),
balanceThreshold = jsonResponse.getDouble("balance_threshold"), balanceThreshold = jsonResponse.getDouble("balance_threshold"),
balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"), balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"),
)) )
)
} catch (e: Exception) {
onFailure(ApiFailure(e.message))
println("API response error: $stringResponse")
}
} else { } else {
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}")) onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
println("API error: $stringResponse") println("API error: $stringResponse")

View file

@ -1,7 +1,8 @@
<resources> <resources>
<string name="app_name" translatable="false">vast.app</string> <string name="app_name" translatable="false">vast.app</string>
<string name="title_activity_dashboard">Dashboard</string> <string name="title_activity_dashboard">Dashboard</string>
<string name="title_activity_login" translatable="false">vast.app</string> <string name="title_activity_login">Login</string>
<string name="title_activity_loading" translatable="false">vast.app</string>
<string name="nav_dashboard">Dashboard</string> <string name="nav_dashboard">Dashboard</string>
<string name="nav_billing">Billing</string> <string name="nav_billing">Billing</string>
<string name="nav_instances">Instances</string> <string name="nav_instances">Instances</string>
@ -21,14 +22,13 @@
<string name="login_checkbox_angry">checkbox is angry</string> <string name="login_checkbox_angry">checkbox is angry</string>
<string name="no_options">none yet sorry</string> <string name="no_options">none yet sorry</string>
<string name="command_permission_denied">If you change your mind, do so from settings</string> <string name="command_permission_denied">If you change your mind, do so from settings</string>
<string name="no_termux">Termuxn\'t</string> <string name="title_activity_termux_ssh">Termux Error</string>
<string name="title_activity_termux_ssh">TermuxSshActivity</string>
<string name="termux_no_ssh">No ssh client on termux, install dropbear or openssh package</string> <string name="termux_no_ssh">No ssh client on termux, install dropbear or openssh package</string>
<string name="copied_to_clipboard">Copied command to clipboard</string> <string name="copied_to_clipboard">Copied command to clipboard</string>
<string name="termux_install_dropbear">Install Dropbear with:</string> <string name="termux_install_dropbear">Install Dropbear with:</string>
<string name="open_termux">Open Termux</string> <string name="open_termux">Open Termux</string>
<string name="termux_not_configured">Termux is not configured for usage with other apps.</string> <string name="termux_not_configured">Termux is not configured for usage with other apps.</string>
<string name="termux_open_instructions">Open instructions on github.com</string> <string name="termux_open_instructions">Open instructions on github.com</string>
<string name="termux_error">An error occured:</string> <string name="termux_error">An error occurred:</string>
<string name="webview_todo">(this will be a webview)</string> <string name="webview_todo">(this will be a webview)</string>
</resources> </resources>