diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f3474b0..2f1bfe3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,7 +3,9 @@ plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) kotlin("plugin.serialization") version "1.9.0" - id("kotlin-kapt") + + alias(libs.plugins.ksp) + alias(libs.plugins.hilt.android) } android { @@ -31,11 +33,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } kotlinOptions { - jvmTarget = "11" + jvmTarget = "21" } buildFeatures { compose = true @@ -43,9 +45,14 @@ android { } dependencies { + implementation(libs.hilt.android) + implementation(libs.hilt.navigation.compose) + ksp(libs.hilt.android.compiler) + implementation(libs.androidx.room.runtime) 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.bcprov.jdk15on) implementation(libs.converter.gson) diff --git a/app/src/main/java/com/acitelight/aether/AetherApp.kt b/app/src/main/java/com/acitelight/aether/AetherApp.kt index 73ca987..7cc7d0c 100644 --- a/app/src/main/java/com/acitelight/aether/AetherApp.kt +++ b/app/src/main/java/com/acitelight/aether/AetherApp.kt @@ -5,9 +5,11 @@ import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.HiltAndroidApp val Context.dataStore: DataStore by preferencesDataStore(name = "configure") +@HiltAndroidApp class AetherApp : Application() { override fun onCreate() { super.onCreate() diff --git a/app/src/main/java/com/acitelight/aether/MainActivity.kt b/app/src/main/java/com/acitelight/aether/MainActivity.kt index 19ce686..7f85263 100644 --- a/app/src/main/java/com/acitelight/aether/MainActivity.kt +++ b/app/src/main/java/com/acitelight/aether/MainActivity.kt @@ -55,10 +55,17 @@ import com.acitelight.aether.view.HomeScreen import com.acitelight.aether.view.MeScreen import com.acitelight.aether.view.VideoPlayer 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() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + window.attributes = window.attributes.apply { screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } diff --git a/app/src/main/java/com/acitelight/aether/service/ApiClient.kt b/app/src/main/java/com/acitelight/aether/service/ApiClient.kt index 122fc4b..937ae3f 100644 --- a/app/src/main/java/com/acitelight/aether/service/ApiClient.kt +++ b/app/src/main/java/com/acitelight/aether/service/ApiClient.kt @@ -1,30 +1,27 @@ package com.acitelight.aether.service -import android.content.Context import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json -import okhttp3.CertificatePinner -import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.io.ByteArrayInputStream -import java.net.HttpURLConnection -import java.net.InetAddress -import java.net.URL import java.security.KeyStore -import java.security.cert.Certificate import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import javax.net.ssl.SSLContext import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager +import okhttp3.EventListener +import java.net.InetAddress +import android.util.Log +import okhttp3.ConnectionSpec object ApiClient { var base: String = "" @@ -34,6 +31,14 @@ object ApiClient { ignoreUnknownKeys = true } + private val dnsEventListener = object : EventListener() { + override fun dnsEnd(call: okhttp3.Call, domainName: String, inetAddressList: List) { + 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 { val certificateFactory = CertificateFactory.getInstance("X.509") val decodedPem = pemString @@ -108,6 +113,7 @@ object ApiClient { } return OkHttpClient.Builder() + .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS)) .sslSocketFactory(sslContext.socketFactory, combinedTm) .build() @@ -116,13 +122,20 @@ object ApiClient { } } - fun createOkHttp(cert: String?): OkHttpClient { - val trustedCert = cert?.let { loadCertificateFromString(it) } - return createOkHttpClientWithDynamicCert(trustedCert) + fun createOkHttp(): OkHttpClient { + return if (cert == "") + OkHttpClient + .Builder() + .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS)) + .eventListener(dnsEventListener) + .build() + else + createOkHttpClientWithDynamicCert(loadCertificateFromString(cert)) + } private fun createRetrofit(): Retrofit { - val okHttpClient = createOkHttpClientWithDynamicCert(loadCertificateFromString(cert)) + val okHttpClient = createOkHttp() return Retrofit.Builder() .baseUrl(base) diff --git a/app/src/main/java/com/acitelight/aether/service/AuthManager.kt b/app/src/main/java/com/acitelight/aether/service/AuthManager.kt index 1a09122..19fdf01 100644 --- a/app/src/main/java/com/acitelight/aether/service/AuthManager.kt +++ b/app/src/main/java/com/acitelight/aether/service/AuthManager.kt @@ -1,10 +1,21 @@ package com.acitelight.aether.service import android.util.Base64 +import android.util.Log import com.acitelight.aether.model.ChallengeResponse +import kotlinx.coroutines.Dispatchers 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.signers.Ed25519Signer +import java.io.IOException +import java.lang.reflect.Proxy +import java.net.InetSocketAddress import java.security.PrivateKey import java.security.Signature @@ -17,13 +28,13 @@ object AuthManager { challengeBase64 = api!!.getChallenge(username).string() }catch (e: Exception) { - print(e.message) + return null } val signedBase64 = signChallenge(db64(privateKey), db64(challengeBase64)) return try { - api!!.verifyChallenge(username, ChallengeResponse(response = signedBase64)).string() + api.verifyChallenge(username, ChallengeResponse(response = signedBase64)).string() } catch (e: Exception) { e.printStackTrace() null diff --git a/app/src/main/java/com/acitelight/aether/service/SettingsDataStoreManager.kt b/app/src/main/java/com/acitelight/aether/service/SettingsDataStoreManager.kt new file mode 100644 index 0000000..8a85e3b --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/service/SettingsDataStoreManager.kt @@ -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 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 = context.dataStore.data.map { preferences -> + preferences[USER_NAME_KEY] ?: "" + } + + val privateKeyFlow: Flow = context.dataStore.data.map { preferences -> + preferences[PRIVATE_KEY] ?: "" + } + + val urlFlow: Flow = context.dataStore.data.map { preferences -> + preferences[URL_KEY] ?: "" + } + + val certFlow: Flow = context.dataStore.data.map { preferences -> + preferences[CERT_KEY] ?: "" + } + + val useSelfSignedFlow: Flow = 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/HomeScreen.kt b/app/src/main/java/com/acitelight/aether/view/HomeScreen.kt index 914ef8d..0b88a5b 100644 --- a/app/src/main/java/com/acitelight/aether/view/HomeScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/HomeScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.acitelight.aether.Global @@ -30,7 +31,7 @@ import com.acitelight.aether.service.RecentManager import com.acitelight.aether.viewModel.HomeScreenViewModel @Composable -fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = viewModel(), navController: NavController) +fun HomeScreen(homeScreenViewModel: HomeScreenViewModel = hiltViewModel(), navController: NavController) { if(Global.loggedIn) homeScreenViewModel.Init() diff --git a/app/src/main/java/com/acitelight/aether/view/MeScreen.kt b/app/src/main/java/com/acitelight/aether/view/MeScreen.kt index 98f43a5..086c486 100644 --- a/app/src/main/java/com/acitelight/aether/view/MeScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/MeScreen.kt @@ -3,11 +3,13 @@ package com.acitelight.aether.view import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.KeyboardOptions 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.material3.Button import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme 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.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel import com.acitelight.aether.viewModel.MeScreenViewModel @Composable -fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) { +fun MeScreen(meScreenViewModel: MeScreenViewModel = hiltViewModel()) { val context = LocalContext.current var username by meScreenViewModel.username; var privateKey by meScreenViewModel.privateKey; var url by meScreenViewModel.url var cert by meScreenViewModel.cert + val uss by meScreenViewModel.uss.collectAsState(initial = false) + LazyColumn( modifier = Modifier .fillMaxSize() @@ -134,6 +140,8 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) { .align(Alignment.Start) ) + Spacer(modifier = Modifier.width(8.dp)) + // Username input field OutlinedTextField( value = url, @@ -146,21 +154,37 @@ fun MeScreen(meScreenViewModel: MeScreenViewModel = viewModel()) { modifier = Modifier.fillMaxWidth() ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(4.dp)) - // Private key input field - OutlinedTextField( - value = cert, - onValueChange = { cert = it }, - label = { Text("Cert") }, - singleLine = false, - maxLines = 40, - minLines = 20, - modifier = Modifier.fillMaxWidth(), - textStyle = TextStyle( - fontSize = 8.sp + Row(Modifier.align(Alignment.Start)) { + Checkbox( + checked = uss, + onCheckedChange = { isChecked -> + meScreenViewModel.onUseSelfSignedCheckedChange(isChecked) + }, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + 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)) diff --git a/app/src/main/java/com/acitelight/aether/viewModel/HomeScreenViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/HomeScreenViewModel.kt index 77278d4..602b2d5 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/HomeScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/HomeScreenViewModel.kt @@ -31,33 +31,19 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map 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 = dataStore.data.map { preferences -> - preferences[USER_NAME_KEY] ?: "" - } - - val privateKeyFlow: Flow = dataStore.data.map { preferences -> - preferences[PRIVATE_KEY] ?: "" - } - - val urlFlow: Flow = dataStore.data.map { preferences -> - preferences[URL_KEY] ?: "" - } - - val certFlow: Flow = dataStore.data.map { preferences -> - preferences[CERT_KEY] ?: "" - } - var _init = false var imageLoader: ImageLoader? = null; + val uss = settingsDataStoreManager.useSelfSignedFlow @Composable fun Init(){ @@ -79,15 +65,15 @@ class HomeScreenViewModel(application: Application) : AndroidViewModel(applicati init { viewModelScope.launch { - val u = userNameFlow.first() - val p = privateKeyFlow.first() - val ur = urlFlow.first() - val c = certFlow.first() + val u = settingsDataStoreManager.userNameFlow.first() + val p = settingsDataStoreManager.privateKeyFlow.first() + val ur = settingsDataStoreManager.urlFlow.first() + val c = settingsDataStoreManager.certFlow.first() - if(u=="" || p=="" || ur=="" || c=="") return@launch + if(u=="" || p=="" || ur=="") return@launch try{ - val usedUrl = ApiClient.apply(ur, c) + val usedUrl = ApiClient.apply(ur, if(uss.first()) c else "") if (MediaManager.token == "null") MediaManager.token = AuthManager.fetchToken( diff --git a/app/src/main/java/com/acitelight/aether/viewModel/MeScreenViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/MeScreenViewModel.kt index 624a6eb..b76098e 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/MeScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/MeScreenViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.acitelight.aether.Global import com.acitelight.aether.dataStore @@ -20,63 +21,52 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map 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) { - 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 = dataStore.data.map { preferences -> - preferences[USER_NAME_KEY] ?: "" - } - - val privateKeyFlow: Flow = dataStore.data.map { preferences -> - preferences[PRIVATE_KEY] ?: "" - } - - val urlFlow: Flow = dataStore.data.map { preferences -> - preferences[URL_KEY] ?: "" - } - - val certFlow: Flow = dataStore.data.map { preferences -> - preferences[CERT_KEY] ?: "" - } +@HiltViewModel +class MeScreenViewModel @Inject constructor( + private val settingsDataStoreManager: SettingsDataStoreManager +) : ViewModel() { val username = mutableStateOf(""); val privateKey = mutableStateOf("") val url = mutableStateOf(""); val cert = mutableStateOf("") + val uss = settingsDataStoreManager.useSelfSignedFlow + init { viewModelScope.launch { - username.value = userNameFlow.first() - privateKey.value = if (privateKeyFlow.first() == "") "" else "******" - url.value = urlFlow.first() - cert.value = certFlow.first() + username.value = settingsDataStoreManager.userNameFlow.first() + privateKey.value = if (settingsDataStoreManager.privateKeyFlow.first() == "") "" else "******" + url.value = settingsDataStoreManager.urlFlow.first() + cert.value = settingsDataStoreManager.certFlow.first() + } + } + + fun onUseSelfSignedCheckedChange(isChecked: Boolean) { + viewModelScope.launch { + settingsDataStoreManager.saveUseSelfSigned(isChecked) } } fun updateServer(u: String, c: String, context: Context) { viewModelScope.launch { - dataStore.edit { preferences -> - preferences[URL_KEY] = u - preferences[CERT_KEY] = c - } + settingsDataStoreManager.saveUrl(u) + settingsDataStoreManager.saveCert(c) Global.loggedIn = false - val us = userNameFlow.first() - val u = urlFlow.first() - val c = certFlow.first() - val p = privateKeyFlow.first() + val us = settingsDataStoreManager.userNameFlow.first() + val p = settingsDataStoreManager.privateKeyFlow.first() - if (u == "" || c == "" || p == "" || us == "") return@launch + if (u == "" || p == "" || us == "") return@launch try { - val usedUrl = ApiClient.apply(u, c) + val usedUrl = ApiClient.apply(u, if(uss.first()) c else "") MediaManager.token = AuthManager.fetchToken( us, p @@ -93,22 +83,18 @@ class MeScreenViewModel(application: Application) : AndroidViewModel(application fun updateAccount(u: String, p: String, context: Context) { viewModelScope.launch { - dataStore.edit { preferences -> - preferences[USER_NAME_KEY] = u - preferences[PRIVATE_KEY] = p - } + settingsDataStoreManager.saveUserName(u) + settingsDataStoreManager.savePrivateKey(p) privateKey.value = "******" Global.loggedIn = false - val u = userNameFlow.first() - val p = privateKeyFlow.first() + val u = settingsDataStoreManager.userNameFlow.first() + val p = settingsDataStoreManager.privateKeyFlow.first() + val ur = settingsDataStoreManager.urlFlow.first() - val ur = urlFlow.first() - val c = certFlow.first() - - if (u == "" || p == "" || ur == "" || c == "") return@launch + if (u == "" || p == "" || ur == "") return@launch try { MediaManager.token = AuthManager.fetchToken( diff --git a/build.gradle.kts b/build.gradle.kts index 952b930..0f85ca8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,7 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false + + alias(libs.plugins.hilt.android) apply false + alias(libs.plugins.ksp) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 71cb063..c23b776 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,15 +8,15 @@ converterGson = "3.0.0" datastorePreferences = "1.1.7" exoplayerplus = "0.2.0" gson = "2.13.1" -kotlin = "2.2.10" +kotlin = "2.2.20" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationJson = "1.9.0" -lifecycleRuntimeKtx = "2.9.2" +lifecycleRuntimeKtx = "2.9.3" activityCompose = "1.10.1" -composeBom = "2025.08.00" +composeBom = "2025.08.01" media3Common = "1.8.0" media3Exoplayer = "1.8.0" media3Ui = "1.8.0" @@ -29,6 +29,10 @@ roomCompiler = "2.7.2" roomKtx = "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] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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" } 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] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", 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" } \ No newline at end of file