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:
parent
9574ed496d
commit
ae4912fd29
19 changed files with 395 additions and 106 deletions
|
@ -14,7 +14,19 @@
|
|||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Vastapp"
|
||||
android:name=".VastApplication"
|
||||
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
|
||||
android:name=".activity.termux.TermuxSshActivity"
|
||||
android:exported="false"
|
||||
|
@ -30,11 +42,6 @@
|
|||
android:exported="true"
|
||||
android:label="@string/title_activity_login"
|
||||
android:theme="@style/Theme.Vastapp">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
38
app/src/main/java/eu/m724/vastapp/VastApplication.kt
Normal file
38
app/src/main/java/eu/m724/vastapp/VastApplication.kt
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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.Screen
|
||||
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 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).enableBrotli(true).build()
|
||||
val vastApi = VastApi(user.apiKey, cronetEngine, executor) // TODO use that from login activity
|
||||
|
||||
val dashboardViewModel = DashboardViewModel(user, vastApi)
|
||||
val dashboardViewModel = DashboardViewModel(application)
|
||||
if (intent.getBooleanExtra("direct", false).not()) {
|
||||
dashboardViewModel.refresh(this)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
dashboardViewModel.refresh(this@DashboardActivity)
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
dashboardViewModel.refreshError.collect {
|
||||
it.forEach { errorMsg ->
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package eu.m724.vastapp.activity.dashboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import eu.m724.vastapp.R
|
||||
import eu.m724.vastapp.VastApplication
|
||||
import eu.m724.vastapp.activity.Opener
|
||||
import eu.m724.vastapp.activity.PermissionChecker
|
||||
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.UserUrlRequestCallback
|
||||
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
|
||||
|
@ -21,30 +21,24 @@ import kotlinx.coroutines.flow.update
|
|||
|
||||
|
||||
class DashboardViewModel(
|
||||
initialUser: User,
|
||||
private val vastApi: VastApi
|
||||
) : ViewModel() { // TODO do something with the user
|
||||
application: Application
|
||||
) : AndroidViewModel(application) { // TODO do something with the user
|
||||
|
||||
private val _uiState: MutableStateFlow<DashboardUiState> =
|
||||
MutableStateFlow(DashboardUiState(0))
|
||||
val uiState: StateFlow<DashboardUiState> =
|
||||
_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())
|
||||
val refreshError: StateFlow<List<String>> = _refreshError.asStateFlow()
|
||||
|
||||
private val _termuxAvailable: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||
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) {
|
||||
_uiState.value = _uiState.value.copy(refreshing = 2)
|
||||
_refreshError.value = emptyList()
|
||||
|
@ -52,7 +46,7 @@ class DashboardViewModel(
|
|||
val userRequest = vastApi.buildRequest(
|
||||
ApiRoute.SHOW_USER,
|
||||
UserUrlRequestCallback({ newUser ->
|
||||
_user.value = newUser
|
||||
account.updateUser(newUser)
|
||||
_uiState.update {
|
||||
it.copy(refreshing = it.refreshing - 1) // TODO I don't like how this looks
|
||||
}
|
||||
|
@ -67,33 +61,17 @@ class DashboardViewModel(
|
|||
val instancesRequest = vastApi.buildRequest(
|
||||
ApiRoute.GET_INSTANCES,
|
||||
InstancesUrlRequestCallback({ instances ->
|
||||
_rentedInstances.value = instances // TODO better way?
|
||||
account.updateRentedInstances(instances)
|
||||
_uiState.update {
|
||||
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 ->
|
||||
_refreshError.update { it + apiFailure.errorMessage!! }
|
||||
_uiState.update {
|
||||
it.copy(refreshing = it.refreshing - 1)
|
||||
}
|
||||
})
|
||||
)
|
||||
) // TODO move all that refreshing to some shared place
|
||||
|
||||
userRequest.start()
|
||||
instancesRequest.start()
|
||||
|
@ -111,7 +89,7 @@ class DashboardViewModel(
|
|||
}
|
||||
} 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")
|
||||
|
@ -123,7 +101,7 @@ class DashboardViewModel(
|
|||
PermissionChecker.requestIfNoPermission(
|
||||
"com.termux.permission.RUN_COMMAND",
|
||||
activity, 0
|
||||
) { granted, asked ->
|
||||
) { granted, _ ->
|
||||
if (granted) {
|
||||
val arguments = arrayOf(
|
||||
"/data/data/com.termux/files/usr/bin/ssh",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package eu.m724.vastapp.activity.dashboard.loading
|
||||
|
||||
data class LoadingResult(
|
||||
val loggedIn: Boolean,
|
||||
val error: String?
|
||||
)
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
*/
|
||||
)
|
|
@ -29,7 +29,7 @@ import eu.m724.vastapp.activity.dashboard.DashboardViewModel
|
|||
fun BillingScreen(dashboardViewModel: DashboardViewModel) {
|
||||
val uiState by dashboardViewModel.uiState.collectAsState()
|
||||
|
||||
val user by dashboardViewModel.user.collectAsState()
|
||||
val user by dashboardViewModel.account.user.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
@ -44,9 +44,9 @@ fun DashboardScreen(dashboardViewModel: DashboardViewModel) {
|
|||
val context = LocalContext.current
|
||||
val uiState by dashboardViewModel.uiState.collectAsState()
|
||||
|
||||
val user by dashboardViewModel.user.collectAsState()
|
||||
val rentedInstances by dashboardViewModel.rentedInstances.collectAsState()
|
||||
val remainingTime by dashboardViewModel.remainingTime.collectAsState()
|
||||
val user by dashboardViewModel.account.user.collectAsState()
|
||||
val rentedInstances by dashboardViewModel.account.rentedInstances.collectAsState()
|
||||
val remainingTime by dashboardViewModel.account.remainingTime.collectAsState()
|
||||
val isRefreshing by remember(uiState) { derivedStateOf { uiState.refreshing > 0 } }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
|
|
@ -42,7 +42,7 @@ fun InstancesScreen(dashboardViewModel: DashboardViewModel) {
|
|||
val activity = LocalContext.current as ComponentActivity
|
||||
|
||||
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()
|
||||
|
||||
// TODO actually get instances
|
||||
|
|
|
@ -55,7 +55,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import eu.m724.vastapp.BuildConfig
|
||||
import eu.m724.vastapp.R
|
||||
import eu.m724.vastapp.activity.dashboard.DashboardActivity
|
||||
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
|
||||
loginViewModel.uiState.collect { state ->
|
||||
if (state is LoginUiState.Success) {
|
||||
|
@ -92,9 +96,6 @@ class LoginActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.AUTO_LOGIN)
|
||||
loginViewModel.loadKey()
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
VastappTheme {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package eu.m724.vastapp.activity.login
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import eu.m724.vastapp.VastApplication
|
||||
import eu.m724.vastapp.vastai.ApiRoute
|
||||
import eu.m724.vastapp.vastai.VastApi
|
||||
import eu.m724.vastapp.vastai.api.UserUrlRequestCallback
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -34,35 +33,16 @@ class LoginViewModel(
|
|||
private val _fullscreenLoading = MutableStateFlow<Boolean>(false)
|
||||
var fullscreenLoading: StateFlow<Boolean> = _fullscreenLoading.asStateFlow()
|
||||
|
||||
private val applicationContext = getApplication<Application>().applicationContext
|
||||
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()
|
||||
}
|
||||
}
|
||||
private val application = getApplication<VastApplication>()
|
||||
|
||||
fun tryLogin() {
|
||||
val apiKey = apiKey.value
|
||||
val vastApi = application.vastApi
|
||||
vastApi.apiKey = apiKey.value
|
||||
|
||||
val vastApi = VastApi(apiKey, cronetEngine, executor)
|
||||
val request = vastApi.buildRequest(
|
||||
ApiRoute.SHOW_USER,
|
||||
UserUrlRequestCallback({ user ->
|
||||
saveKey() // TODO toggle for this
|
||||
application.submitKey(apiKey.value) // TODO toggle for this
|
||||
_uiState.value = LoginUiState.Success(user)
|
||||
}, { apiFailure ->
|
||||
_uiState.value = LoginUiState.Idle
|
||||
|
|
49
app/src/main/java/eu/m724/vastapp/vastai/Account.kt
Normal file
49
app/src/main/java/eu/m724/vastapp/vastai/Account.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
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
|
||||
var apiKey: String, // TODO make private?
|
||||
private val cronetEngine: CronetEngine,
|
||||
private val executor: Executor
|
||||
) {
|
||||
/**
|
||||
* build an api request
|
||||
|
|
|
@ -43,16 +43,23 @@ class UserUrlRequestCallback(
|
|||
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
println(stringResponse) // TODO don't do that
|
||||
if (info?.httpStatusCode == 200) {
|
||||
val jsonResponse = JSONObject(stringResponse.toString())
|
||||
onSuccess(User(
|
||||
id = jsonResponse.getString("id"),
|
||||
username = jsonResponse.getString("username"),
|
||||
email = jsonResponse.getString("email"),
|
||||
apiKey = jsonResponse.getString("api_key"),
|
||||
credit = jsonResponse.getDouble("credit"),
|
||||
balanceThreshold = jsonResponse.getDouble("balance_threshold"),
|
||||
balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"),
|
||||
))
|
||||
try {
|
||||
val jsonResponse = JSONObject(stringResponse.toString())
|
||||
onSuccess(
|
||||
User(
|
||||
id = jsonResponse.getString("id"),
|
||||
username = jsonResponse.getString("username"),
|
||||
email = jsonResponse.getString("email"),
|
||||
apiKey = jsonResponse.getString("api_key"),
|
||||
credit = jsonResponse.getDouble("credit"),
|
||||
balanceThreshold = jsonResponse.getDouble("balance_threshold"),
|
||||
balanceThresholdEnabled = jsonResponse.getBoolean("balance_threshold_enabled"),
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
onFailure(ApiFailure(e.message))
|
||||
println("API response error: $stringResponse")
|
||||
}
|
||||
} else {
|
||||
onFailure(ApiFailure("${info?.httpStatusCode} ${info?.httpStatusText}"))
|
||||
println("API error: $stringResponse")
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<resources>
|
||||
<string name="app_name" translatable="false">vast.app</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_billing">Billing</string>
|
||||
<string name="nav_instances">Instances</string>
|
||||
|
@ -21,14 +22,13 @@
|
|||
<string name="login_checkbox_angry">checkbox is angry</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="no_termux">Termuxn\'t</string>
|
||||
<string name="title_activity_termux_ssh">TermuxSshActivity</string>
|
||||
<string name="title_activity_termux_ssh">Termux Error</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="termux_install_dropbear">Install Dropbear with:</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_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>
|
||||
</resources>
|
Loading…
Reference in a new issue