[feat] Optional self signed certificate

This commit is contained in:
acite
2025-09-10 23:51:13 +08:00
parent b48f8ce6b0
commit 10f316cb48
12 changed files with 246 additions and 111 deletions

View File

@@ -3,7 +3,9 @@ plugins {
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "1.9.0" kotlin("plugin.serialization") version "1.9.0"
id("kotlin-kapt")
alias(libs.plugins.ksp)
alias(libs.plugins.hilt.android)
} }
android { android {
@@ -31,11 +33,11 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_21
} }
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "21"
} }
buildFeatures { buildFeatures {
compose = true compose = true
@@ -43,9 +45,14 @@ android {
} }
dependencies { dependencies {
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
ksp(libs.hilt.android.compiler)
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.ktx)
kapt("androidx.room:room-compiler:2.7.2") ksp(libs.androidx.room.compiler)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.bcprov.jdk15on) implementation(libs.bcprov.jdk15on)
implementation(libs.converter.gson) implementation(libs.converter.gson)

View File

@@ -5,9 +5,11 @@ import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.HiltAndroidApp
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "configure") val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "configure")
@HiltAndroidApp
class AetherApp : Application() { class AetherApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View File

@@ -55,10 +55,17 @@ import com.acitelight.aether.view.HomeScreen
import com.acitelight.aether.view.MeScreen import com.acitelight.aether.view.MeScreen
import com.acitelight.aether.view.VideoPlayer import com.acitelight.aether.view.VideoPlayer
import com.acitelight.aether.view.VideoScreen import com.acitelight.aether.view.VideoScreen
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
@AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.attributes = window.attributes.apply { window.attributes = window.attributes.apply {
screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
} }

View File

@@ -1,30 +1,27 @@
package com.acitelight.aether.service package com.acitelight.aether.service
import android.content.Context
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.CertificatePinner
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.URL
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
import okhttp3.EventListener
import java.net.InetAddress
import android.util.Log
import okhttp3.ConnectionSpec
object ApiClient { object ApiClient {
var base: String = "" var base: String = ""
@@ -34,6 +31,14 @@ object ApiClient {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }
private val dnsEventListener = object : EventListener() {
override fun dnsEnd(call: okhttp3.Call, domainName: String, inetAddressList: List<InetAddress>) {
super.dnsEnd(call, domainName, inetAddressList)
val ipAddresses = inetAddressList.joinToString(", ") { it.hostAddress }
Log.d("OkHttp_DNS", "Domain '$domainName' resolved to IPs: [$ipAddresses]")
}
}
fun loadCertificateFromString(pemString: String): X509Certificate { fun loadCertificateFromString(pemString: String): X509Certificate {
val certificateFactory = CertificateFactory.getInstance("X.509") val certificateFactory = CertificateFactory.getInstance("X.509")
val decodedPem = pemString val decodedPem = pemString
@@ -108,6 +113,7 @@ object ApiClient {
} }
return OkHttpClient.Builder() return OkHttpClient.Builder()
.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.sslSocketFactory(sslContext.socketFactory, combinedTm) .sslSocketFactory(sslContext.socketFactory, combinedTm)
.build() .build()
@@ -116,13 +122,20 @@ object ApiClient {
} }
} }
fun createOkHttp(cert: String?): OkHttpClient { fun createOkHttp(): OkHttpClient {
val trustedCert = cert?.let { loadCertificateFromString(it) } return if (cert == "")
return createOkHttpClientWithDynamicCert(trustedCert) OkHttpClient
.Builder()
.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.eventListener(dnsEventListener)
.build()
else
createOkHttpClientWithDynamicCert(loadCertificateFromString(cert))
} }
private fun createRetrofit(): Retrofit { private fun createRetrofit(): Retrofit {
val okHttpClient = createOkHttpClientWithDynamicCert(loadCertificateFromString(cert)) val okHttpClient = createOkHttp()
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(base) .baseUrl(base)

View File

@@ -1,10 +1,21 @@
package com.acitelight.aether.service package com.acitelight.aether.service
import android.util.Base64 import android.util.Base64
import android.util.Log
import com.acitelight.aether.model.ChallengeResponse import com.acitelight.aether.model.ChallengeResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.Call
import okhttp3.EventListener
import okhttp3.Handshake
import okhttp3.OkHttpClient
import okhttp3.Request
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
import org.bouncycastle.crypto.signers.Ed25519Signer import org.bouncycastle.crypto.signers.Ed25519Signer
import java.io.IOException
import java.lang.reflect.Proxy
import java.net.InetSocketAddress
import java.security.PrivateKey import java.security.PrivateKey
import java.security.Signature import java.security.Signature
@@ -17,13 +28,13 @@ object AuthManager {
challengeBase64 = api!!.getChallenge(username).string() challengeBase64 = api!!.getChallenge(username).string()
}catch (e: Exception) }catch (e: Exception)
{ {
print(e.message) return null
} }
val signedBase64 = signChallenge(db64(privateKey), db64(challengeBase64)) val signedBase64 = signChallenge(db64(privateKey), db64(challengeBase64))
return try { return try {
api!!.verifyChallenge(username, ChallengeResponse(response = signedBase64)).string() api.verifyChallenge(username, ChallengeResponse(response = signedBase64)).string()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
null null

View File

@@ -0,0 +1,85 @@
package com.acitelight.aether.service
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
@Singleton
class SettingsDataStoreManager @Inject constructor(
@ApplicationContext private val context: Context
) {
companion object {
val USER_NAME_KEY = stringPreferencesKey("user_name")
val PRIVATE_KEY = stringPreferencesKey("private_key")
val URL_KEY = stringPreferencesKey("url")
val CERT_KEY = stringPreferencesKey("cert")
val USE_SELF_SIGNED_KEY = booleanPreferencesKey("use_self_signed")
}
val userNameFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[USER_NAME_KEY] ?: ""
}
val privateKeyFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[PRIVATE_KEY] ?: ""
}
val urlFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[URL_KEY] ?: ""
}
val certFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[CERT_KEY] ?: ""
}
val useSelfSignedFlow: Flow<Boolean> = context.dataStore.data.map { preferences ->
preferences[USE_SELF_SIGNED_KEY] ?: false
}
suspend fun saveUserName(name: String) {
context.dataStore.edit { preferences ->
preferences[USER_NAME_KEY] = name
}
}
suspend fun savePrivateKey(key: String) {
context.dataStore.edit { preferences ->
preferences[PRIVATE_KEY] = key
}
}
suspend fun saveUrl(url: String) {
context.dataStore.edit { preferences ->
preferences[URL_KEY] = url
}
}
suspend fun saveCert(cert: String) {
context.dataStore.edit { preferences ->
preferences[CERT_KEY] = cert
}
}
suspend fun saveUseSelfSigned(useSelfSigned: Boolean) {
context.dataStore.edit { preferences ->
preferences[USE_SELF_SIGNED_KEY] = useSelfSigned
}
}
suspend fun clearAll() {
context.dataStore.edit { preferences ->
preferences.clear()
}
}
}

View File

@@ -22,6 +22,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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 androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.acitelight.aether.Global import com.acitelight.aether.Global
@@ -30,7 +31,7 @@ import com.acitelight.aether.service.RecentManager
import com.acitelight.aether.viewModel.HomeScreenViewModel import com.acitelight.aether.viewModel.HomeScreenViewModel
@Composable @Composable
fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = viewModel(), navController: NavController) fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), navController: NavController)
{ {
if(Global.loggedIn) if(Global.loggedIn)
homeScreenViewModel.Init() homeScreenViewModel.Init()

View File

@@ -3,11 +3,13 @@ package com.acitelight.aether.view
import android.widget.Toast import android.widget.Toast
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.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -17,6 +19,7 @@ import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -32,17 +35,20 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
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 androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.acitelight.aether.viewModel.MeScreenViewModel import com.acitelight.aether.viewModel.MeScreenViewModel
@Composable @Composable
fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) { fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel()) {
val context = LocalContext.current val context = LocalContext.current
var username by meScreenViewModel.username; var username by meScreenViewModel.username;
var privateKey by meScreenViewModel.privateKey; var privateKey by meScreenViewModel.privateKey;
var url by meScreenViewModel.url var url by meScreenViewModel.url
var cert by meScreenViewModel.cert var cert by meScreenViewModel.cert
val uss by meScreenViewModel.uss.collectAsState(initial = false)
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -134,6 +140,8 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) {
.align(Alignment.Start) .align(Alignment.Start)
) )
Spacer(modifier = Modifier.width(8.dp))
// Username input field // Username input field
OutlinedTextField( OutlinedTextField(
value = url, value = url,
@@ -146,21 +154,37 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(4.dp))
// Private key input field Row(Modifier.align(Alignment.Start)) {
OutlinedTextField( Checkbox(
value = cert, checked = uss,
onValueChange = { cert = it }, onCheckedChange = { isChecked ->
label = { Text("Cert") }, meScreenViewModel.onUseSelfSignedCheckedChange(isChecked)
singleLine = false, },
maxLines = 40, modifier = Modifier.align(Alignment.CenterVertically)
minLines = 20, )
modifier = Modifier.fillMaxWidth(), Spacer(modifier = Modifier.width(4.dp))
textStyle = TextStyle( Text(
fontSize = 8.sp text = "Use Self-Signed Cert",
modifier = Modifier.align(Alignment.CenterVertically)
)
}
Spacer(modifier = Modifier.height(4.dp))
// Private key input field
if (uss)
OutlinedTextField(
value = cert,
onValueChange = { cert = it },
label = { Text("Cert") },
singleLine = false,
maxLines = 40,
minLines = 20,
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(
fontSize = 8.sp
)
) )
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))

View File

@@ -31,33 +31,19 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
import com.acitelight.aether.service.*
import dagger.hilt.android.lifecycle.HiltViewModel
class HomeScreenViewModel(application: Application) : AndroidViewModel(application)
@HiltViewModel
class HomeScreenViewModel @Inject constructor(
private val settingsDataStoreManager: SettingsDataStoreManager
) : ViewModel()
{ {
private val dataStore = application.dataStore
private val USER_NAME_KEY = stringPreferencesKey("user_name")
private val PRIVATE_KEY = stringPreferencesKey("private_key")
private val URL_KEY = stringPreferencesKey("url")
private val CERT_KEY = stringPreferencesKey("cert")
val userNameFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[USER_NAME_KEY] ?: ""
}
val privateKeyFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[PRIVATE_KEY] ?: ""
}
val urlFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[URL_KEY] ?: ""
}
val certFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[CERT_KEY] ?: ""
}
var _init = false var _init = false
var imageLoader: ImageLoader? = null; var imageLoader: ImageLoader? = null;
val uss = settingsDataStoreManager.useSelfSignedFlow
@Composable @Composable
fun Init(){ fun Init(){
@@ -79,15 +65,15 @@ class HomeScreenViewModel(application: Application) : AndroidViewModel(applicati
init { init {
viewModelScope.launch { viewModelScope.launch {
val u = userNameFlow.first() val u = settingsDataStoreManager.userNameFlow.first()
val p = privateKeyFlow.first() val p = settingsDataStoreManager.privateKeyFlow.first()
val ur = urlFlow.first() val ur = settingsDataStoreManager.urlFlow.first()
val c = certFlow.first() val c = settingsDataStoreManager.certFlow.first()
if(u=="" || p=="" || ur=="" || c=="") return@launch if(u=="" || p=="" || ur=="") return@launch
try{ try{
val usedUrl = ApiClient.apply(ur, c) val usedUrl = ApiClient.apply(ur, if(uss.first()) c else "")
if (MediaManager.token == "null") if (MediaManager.token == "null")
MediaManager.token = AuthManager.fetchToken( MediaManager.token = AuthManager.fetchToken(

View File

@@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.acitelight.aether.Global import com.acitelight.aether.Global
import com.acitelight.aether.dataStore import com.acitelight.aether.dataStore
@@ -20,63 +21,52 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
import com.acitelight.aether.service.*
import dagger.hilt.android.lifecycle.HiltViewModel
class MeScreenViewModel(application: Application) : AndroidViewModel(application) { @HiltViewModel
private val dataStore = application.dataStore class MeScreenViewModel @Inject constructor(
private val USER_NAME_KEY = stringPreferencesKey("user_name") private val settingsDataStoreManager: SettingsDataStoreManager
private val PRIVATE_KEY = stringPreferencesKey("private_key") ) : ViewModel() {
private val URL_KEY = stringPreferencesKey("url")
private val CERT_KEY = stringPreferencesKey("cert")
val userNameFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[USER_NAME_KEY] ?: ""
}
val privateKeyFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[PRIVATE_KEY] ?: ""
}
val urlFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[URL_KEY] ?: ""
}
val certFlow: Flow<String> = dataStore.data.map { preferences ->
preferences[CERT_KEY] ?: ""
}
val username = mutableStateOf(""); val username = mutableStateOf("");
val privateKey = mutableStateOf("") val privateKey = mutableStateOf("")
val url = mutableStateOf(""); val url = mutableStateOf("");
val cert = mutableStateOf("") val cert = mutableStateOf("")
val uss = settingsDataStoreManager.useSelfSignedFlow
init { init {
viewModelScope.launch { viewModelScope.launch {
username.value = userNameFlow.first() username.value = settingsDataStoreManager.userNameFlow.first()
privateKey.value = if (privateKeyFlow.first() == "") "" else "******" privateKey.value = if (settingsDataStoreManager.privateKeyFlow.first() == "") "" else "******"
url.value = urlFlow.first() url.value = settingsDataStoreManager.urlFlow.first()
cert.value = certFlow.first() cert.value = settingsDataStoreManager.certFlow.first()
}
}
fun onUseSelfSignedCheckedChange(isChecked: Boolean) {
viewModelScope.launch {
settingsDataStoreManager.saveUseSelfSigned(isChecked)
} }
} }
fun updateServer(u: String, c: String, context: Context) fun updateServer(u: String, c: String, context: Context)
{ {
viewModelScope.launch { viewModelScope.launch {
dataStore.edit { preferences -> settingsDataStoreManager.saveUrl(u)
preferences[URL_KEY] = u settingsDataStoreManager.saveCert(c)
preferences[CERT_KEY] = c
}
Global.loggedIn = false Global.loggedIn = false
val us = userNameFlow.first() val us = settingsDataStoreManager.userNameFlow.first()
val u = urlFlow.first() val p = settingsDataStoreManager.privateKeyFlow.first()
val c = certFlow.first()
val p = privateKeyFlow.first()
if (u == "" || c == "" || p == "" || us == "") return@launch if (u == "" || p == "" || us == "") return@launch
try { try {
val usedUrl = ApiClient.apply(u, c) val usedUrl = ApiClient.apply(u, if(uss.first()) c else "")
MediaManager.token = AuthManager.fetchToken( MediaManager.token = AuthManager.fetchToken(
us, us,
p p
@@ -93,22 +83,18 @@ class MeScreenViewModel(application: Application) : AndroidViewModel(application
fun updateAccount(u: String, p: String, context: Context) { fun updateAccount(u: String, p: String, context: Context) {
viewModelScope.launch { viewModelScope.launch {
dataStore.edit { preferences -> settingsDataStoreManager.saveUserName(u)
preferences[USER_NAME_KEY] = u settingsDataStoreManager.savePrivateKey(p)
preferences[PRIVATE_KEY] = p
}
privateKey.value = "******" privateKey.value = "******"
Global.loggedIn = false Global.loggedIn = false
val u = userNameFlow.first() val u = settingsDataStoreManager.userNameFlow.first()
val p = privateKeyFlow.first() val p = settingsDataStoreManager.privateKeyFlow.first()
val ur = settingsDataStoreManager.urlFlow.first()
val ur = urlFlow.first() if (u == "" || p == "" || ur == "") return@launch
val c = certFlow.first()
if (u == "" || p == "" || ur == "" || c == "") return@launch
try { try {
MediaManager.token = AuthManager.fetchToken( MediaManager.token = AuthManager.fetchToken(

View File

@@ -3,4 +3,7 @@ plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.hilt.android) apply false
alias(libs.plugins.ksp) apply false
} }

View File

@@ -8,15 +8,15 @@ converterGson = "3.0.0"
datastorePreferences = "1.1.7" datastorePreferences = "1.1.7"
exoplayerplus = "0.2.0" exoplayerplus = "0.2.0"
gson = "2.13.1" gson = "2.13.1"
kotlin = "2.2.10" kotlin = "2.2.20"
coreKtx = "1.17.0" coreKtx = "1.17.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0" kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.2" lifecycleRuntimeKtx = "2.9.3"
activityCompose = "1.10.1" activityCompose = "1.10.1"
composeBom = "2025.08.00" composeBom = "2025.08.01"
media3Common = "1.8.0" media3Common = "1.8.0"
media3Exoplayer = "1.8.0" media3Exoplayer = "1.8.0"
media3Ui = "1.8.0" media3Ui = "1.8.0"
@@ -29,6 +29,10 @@ roomCompiler = "2.7.2"
roomKtx = "2.7.2" roomKtx = "2.7.2"
roomRuntime = "2.7.2" roomRuntime = "2.7.2"
ksp = "2.1.21-2.0.2"
hilt = "2.57.1"
hilt-navigation-compose = "1.2.0"
[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" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
@@ -64,8 +68,14 @@ retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
androidx-media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3DatasourceOkhttp" } androidx-media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3DatasourceOkhttp" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }