[feat] Optional self signed certificate
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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" }
|
||||||
Reference in New Issue
Block a user