diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..e3b2cfae --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.kt] +ktlint_function_naming_ignore_when_annotated_with=Composable diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 31ed7477..166793fa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,10 +1,13 @@ +import com.google.protobuf.gradle.id + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) - alias(libs.plugins.protobuf) alias(libs.plugins.ksp) alias(libs.plugins.kotlin.compose) alias(libs.plugins.hilt) + alias(libs.plugins.room) + alias(libs.plugins.protobuf) } android { @@ -24,9 +27,11 @@ android { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } + debug { + } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -39,6 +44,29 @@ android { buildConfig = true compose = true } + room { + schemaDirectory("$projectDir/schemas") + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${libs.protobuf.kotlin.lite.get().version}" + } + generateProtoTasks { + all().forEach { + it.plugins { + id("java") { + option("lite") + } + } + it.builtins { + id("kotlin") { + option("lite") + } + } + } + } } dependencies { @@ -67,7 +95,9 @@ dependencies { implementation(libs.coil.gif) implementation(libs.coil.svg) - implementation(libs.jellyfin.sdk) + implementation(libs.jellyfin.core) + implementation(libs.jellyfin.api) + implementation(libs.jellyfin.api.okhttp) implementation(libs.androidx.navigation3.ui) implementation(libs.androidx.navigation3.runtime) @@ -77,10 +107,14 @@ dependencies { implementation(libs.kotlinx.serialization.json) implementation(libs.hilt.android) + implementation(libs.androidx.room.common.jvm) + implementation(libs.androidx.room.ktx) + implementation(libs.androidx.compose.material3) + ksp(libs.androidx.room.compiler) ksp(libs.hilt.android.compiler) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/DolphinApplication.kt b/app/src/main/java/com/github/damontecres/dolphin/DolphinApplication.kt index c1cf9714..bb8490b7 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/DolphinApplication.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/DolphinApplication.kt @@ -4,14 +4,13 @@ import android.app.Application import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class DolphinApplication: Application() { +class DolphinApplication : Application() { init { instance = this } - companion object{ + companion object { lateinit var instance: DolphinApplication private set } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt index 7ba1d88d..919e21b7 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt @@ -1,50 +1,64 @@ package com.github.damontecres.dolphin import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize -import androidx.tv.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.tooling.preview.Preview +import androidx.datastore.core.DataStore import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.Surface +import androidx.tv.material3.Text +import com.github.damontecres.dolphin.data.ServerRepository +import com.github.damontecres.dolphin.preferences.UserPreferences +import com.github.damontecres.dolphin.ui.ServerLoginPage import com.github.damontecres.dolphin.ui.theme.DolphinTheme import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { + @Inject + lateinit var serverRepository: ServerRepository + + @Inject + lateinit var userPreferencesDataStore: DataStore + @OptIn(ExperimentalTvMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { + val scope = rememberCoroutineScope() DolphinTheme { Surface( modifier = Modifier.fillMaxSize(), - shape = RectangleShape + shape = RectangleShape, ) { - Greeting("Android") + val preferences by userPreferencesDataStore.data.collectAsState(null) + preferences?.let { preferences -> + if (preferences.currentServerId.isNotBlank() && preferences.currentUserId.isNotBlank()) { + scope.launch { + serverRepository.restoreSession( + preferences.currentServerId, + preferences.currentUserId, + ) + } + } else { + ServerLoginPage(modifier = Modifier.fillMaxSize()) + } + val server = serverRepository.currentServer + val user = serverRepository.currentUser + if (server != null && user != null) { + Text("Logged in as ${user.name} on ${server.url}") + } + } } } } } } - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - DolphinTheme { - Greeting("Android") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt b/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt new file mode 100644 index 00000000..aa1fd500 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt @@ -0,0 +1,9 @@ +package com.github.damontecres.dolphin.data + +import androidx.room.Database +import androidx.room.RoomDatabase + +@Database(entities = [JellyfinServer::class, JellyfinUser::class], version = 1, exportSchema = false) +abstract class DolphinDatabase : RoomDatabase() { + abstract fun serverDao(): JellyfinServerDao +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt b/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt new file mode 100644 index 00000000..bfc76da8 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt @@ -0,0 +1,76 @@ +package com.github.damontecres.dolphin.data + +import androidx.room.ColumnInfo +import androidx.room.Dao +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query +import androidx.room.Relation +import androidx.room.Transaction + +@Entity(tableName = "servers") +data class JellyfinServer( + @PrimaryKey val id: String, + val name: String, + val url: String, +) + +@Entity( + tableName = "users", + primaryKeys = ["id", "serverId"], + foreignKeys = [ + ForeignKey( + entity = JellyfinServer::class, + parentColumns = arrayOf("id"), + childColumns = arrayOf("serverId"), + onDelete = ForeignKey.CASCADE, + ), + ], +) +data class JellyfinUser( + @ColumnInfo(index = true) + val id: String, + val name: String?, + @ColumnInfo(index = true) + val serverId: String, + val accessToken: String, +) + +data class JellyfinServerUsers( + @Embedded val server: JellyfinServer, + @Relation( + parentColumn = "id", + entityColumn = "serverId", + ) + val users: List, +) + +@Dao +interface JellyfinServerDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addServer(vararg servers: JellyfinServer) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addUser(vararg users: JellyfinUser) + + @Query("DELETE FROM servers WHERE id = :serverId") + fun deleteServer(serverId: String) + + @Query("DELETE FROM users WHERE serverId = :serverId AND id = :userId") + fun deleteUser( + serverId: String, + userId: String, + ) + + @Transaction + @Query("SELECT * FROM servers") + fun getServers(): List + + @Transaction + @Query("SELECT * FROM servers WHERE id = :serverId") + fun getServer(serverId: String): JellyfinServerUsers? +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt new file mode 100644 index 00000000..c20886a4 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt @@ -0,0 +1,89 @@ +package com.github.damontecres.dolphin.data + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.exception.InvalidStatusException +import org.jellyfin.sdk.api.client.extensions.userApi +import javax.inject.Inject + +class ServerRepository + @Inject + constructor( + val serverDao: JellyfinServerDao, + val apiClient: ApiClient, + ) { + private var _currentServer by mutableStateOf(null) + val currentServer get() = _currentServer + private var _currentUser by mutableStateOf(null) + val currentUser get() = _currentUser + + suspend fun addAndChangeServer(server: JellyfinServer) { + withContext(Dispatchers.IO) { + serverDao.addServer(server) + } + changeServer(server) + } + + suspend fun addAndChangeUser( + server: JellyfinServer, + user: JellyfinUser, + ) { + if (server.id != user.serverId) { + throw IllegalStateException() + } + withContext(Dispatchers.IO) { + serverDao.addServer(server) + serverDao.addUser(user) + } + changeUser(server, user) + } + + fun changeServer(server: JellyfinServer) { + apiClient.update(baseUrl = server.url, accessToken = null) + _currentServer = server + } + + suspend fun changeUser( + server: JellyfinServer, + user: JellyfinUser, + ) { + apiClient.update(baseUrl = server.url, accessToken = user.accessToken) + try { + apiClient.userApi + .getCurrentUser() + .content.name + _currentServer = server + _currentUser = user + } catch (e: InvalidStatusException) { + // TODO + if (e.status == 401) { + // Unauthorized + _currentServer = null + _currentUser = null + return + } + } + } + + suspend fun restoreSession( + serverId: String, + userId: String, + ): Boolean { + val serverAndUsers = + withContext(Dispatchers.IO) { + serverDao.getServer(serverId) + } + if (serverAndUsers != null) { + val user = serverAndUsers.users.firstOrNull { it.id == userId } + if (user != null) { + changeUser(serverAndUsers.server, user) + return true + } + } + return false + } + } diff --git a/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt b/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt new file mode 100644 index 00000000..c2d61b27 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt @@ -0,0 +1,53 @@ +package com.github.damontecres.dolphin.hilt + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.dataStoreFile +import androidx.room.Room +import com.github.damontecres.dolphin.data.DolphinDatabase +import com.github.damontecres.dolphin.data.JellyfinServerDao +import com.github.damontecres.dolphin.preferences.UserPreferences +import com.github.damontecres.dolphin.preferences.UserPreferencesSerializer +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + @Provides + @Singleton + fun database( + @ApplicationContext context: Context, + ): DolphinDatabase = + Room + .databaseBuilder( + context, + DolphinDatabase::class.java, + "dolphin", + ).build() + + @Provides + @Singleton + fun serverDao(db: DolphinDatabase): JellyfinServerDao = db.serverDao() + + @Provides + @Singleton + fun userPreferencesDataStore( + @ApplicationContext context: Context, + userPreferencesSerializer: UserPreferencesSerializer, + ): DataStore = + DataStoreFactory.create( + serializer = userPreferencesSerializer, + produceFile = { context.dataStoreFile("user_preferences.pb") }, + corruptionHandler = + ReplaceFileCorruptionHandler( + produceNewData = { UserPreferences.getDefaultInstance() }, + ), + ) +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/hilt/DolphinModule.kt b/app/src/main/java/com/github/damontecres/dolphin/hilt/DolphinModule.kt new file mode 100644 index 00000000..b54ae779 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/hilt/DolphinModule.kt @@ -0,0 +1,57 @@ +package com.github.damontecres.dolphin.hilt + +import android.annotation.SuppressLint +import android.content.Context +import android.provider.Settings +import com.github.damontecres.dolphin.BuildConfig +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import org.jellyfin.sdk.Jellyfin +import org.jellyfin.sdk.api.okhttp.OkHttpFactory +import org.jellyfin.sdk.createJellyfin +import org.jellyfin.sdk.model.ClientInfo +import org.jellyfin.sdk.model.DeviceInfo + +@Module +@InstallIn(SingletonComponent::class) +object DolphinModule { + @Provides + fun okHttpClient() = + OkHttpClient + .Builder() + .apply { + // TODO user agent, timeouts, logging, etc + }.build() + + @Provides + fun okHttpFactory(okHttpClient: OkHttpClient) = OkHttpFactory(okHttpClient) + + @Provides + fun jellyfin( + okHttpFactory: OkHttpFactory, + @ApplicationContext context: Context, + ): Jellyfin = + createJellyfin { + this.context = context + clientInfo = + ClientInfo( + name = "Dolphin", + version = BuildConfig.VERSION_NAME, + ) + deviceInfo = + DeviceInfo( + id = @SuppressLint("HardwareIds") Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID), + name = Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME), + ) + apiClientFactory = okHttpFactory + socketConnectionFactory = okHttpFactory + minimumServerVersion = Jellyfin.minimumVersion + } + + @Provides + fun apiClient(jellyfin: Jellyfin) = jellyfin.createApi() +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/preferences/UserPreferencesSeralizer.kt b/app/src/main/java/com/github/damontecres/dolphin/preferences/UserPreferencesSeralizer.kt new file mode 100644 index 00000000..cc15197c --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/preferences/UserPreferencesSeralizer.kt @@ -0,0 +1,27 @@ +package com.github.damontecres.dolphin.preferences + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import com.google.protobuf.InvalidProtocolBufferException +import java.io.InputStream +import java.io.OutputStream +import javax.inject.Inject + +class UserPreferencesSerializer + @Inject + constructor() : Serializer { + override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): UserPreferences { + try { + return UserPreferences.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + } + + override suspend fun writeTo( + t: UserPreferences, + output: OutputStream, + ) = t.writeTo(output) + } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/MainPage.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/MainPage.kt new file mode 100644 index 00000000..fe7f144f --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/MainPage.kt @@ -0,0 +1,39 @@ +package com.github.damontecres.dolphin.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import com.github.damontecres.dolphin.preferences.UserPreferences +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.userApi +import javax.inject.Inject + +@HiltViewModel +class MainViewModel + @Inject + constructor( + val api: ApiClient, + ) : ViewModel() { + init { + viewModelScope.launch { + api.userApi.getCurrentUser().content.configuration?.let { + it.orderedViews + } + } + } + + fun todo() { + } + } + +@Composable +fun MainPage( + preferences: UserPreferences, + modifier: Modifier, + viewModel: MainViewModel = viewModel(), +) { +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt new file mode 100644 index 00000000..871d5ec0 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt @@ -0,0 +1,159 @@ +package com.github.damontecres.dolphin.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.datastore.core.DataStore +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.tv.material3.Button +import androidx.tv.material3.Text +import com.github.damontecres.dolphin.data.JellyfinServer +import com.github.damontecres.dolphin.data.JellyfinUser +import com.github.damontecres.dolphin.data.ServerRepository +import com.github.damontecres.dolphin.preferences.UserPreferences +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.jellyfin.sdk.Jellyfin +import org.jellyfin.sdk.api.client.exception.InvalidStatusException +import org.jellyfin.sdk.api.client.extensions.authenticateUserByName +import org.jellyfin.sdk.api.client.extensions.userApi +import javax.inject.Inject + +@HiltViewModel +class ServerManagementViewModel + @Inject + constructor( + val serverRepository: ServerRepository, + val userPreferencesDataStore: DataStore, + val jellyfin: Jellyfin, + ) : ViewModel() { + fun addServer() { + } + + suspend fun loginWithPassword( + serverUrl: String, + username: String, + password: String, + ) { + try { + val api = jellyfin.createApi(baseUrl = serverUrl) + val authenticationResult by api.userApi.authenticateUserByName( + username = username, + password = password, + ) + + val accessToken = authenticationResult.accessToken + if (accessToken != null) { + val server = + authenticationResult.serverId?.let { + JellyfinServer( + id = it, + name = serverUrl, + url = serverUrl, + ) + } + if (server != null) { + val user = + authenticationResult.user?.let { + JellyfinUser( + id = it.id.toString(), + name = it.name, + serverId = server.id, + accessToken = accessToken, + ) + } + if (user != null) { + userPreferencesDataStore.updateData { + it + .toBuilder() + .apply { + currentServerId = server.id + currentUserId = user.id + }.build() + } + serverRepository.addAndChangeUser(server, user) + } + } + } + + // Print session information + println(authenticationResult.sessionInfo) + } catch (err: InvalidStatusException) { + if (err.status == 401) { + // Username or password is incorrect + println("Invalid user") + } + } + } + } + +@Composable +fun ServerLoginPage( + modifier: Modifier = Modifier, + viewModel: ServerManagementViewModel = viewModel(), +) { + val scope = rememberCoroutineScope() + ServerLoginForm( + modifier = modifier, + onSubmit = { serverUrl, username, password -> + scope.launch { + // Handle form submission + viewModel.loginWithPassword(serverUrl, username, password) + } + }, + ) +} + +@Composable +fun ServerLoginForm( + modifier: Modifier = Modifier, + onSubmit: (serverUrl: String, username: String, password: String) -> Unit, +) { + var serverUrl by remember { mutableStateOf("") } + var username by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + Column(modifier = modifier.padding(16.dp)) { + TextField( + value = serverUrl, + onValueChange = { serverUrl = it }, + label = { Text("Server URL") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(8.dp)) + TextField( + value = username, + onValueChange = { username = it }, + label = { Text("Username") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(8.dp)) + TextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { onSubmit(serverUrl, username, password) }, + modifier = Modifier.fillMaxWidth(), + ) { + Text("Submit") + } + } +} diff --git a/app/src/main/proto/DolphinDataStore.proto b/app/src/main/proto/DolphinDataStore.proto new file mode 100644 index 00000000..cb6762e2 --- /dev/null +++ b/app/src/main/proto/DolphinDataStore.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "com.github.damontecres.dolphin.preferences"; +option java_multiple_files = true; + +message UserPreferences { + // The currently signed in server and user IDs, mostly for restoring a session + string current_server_id = 1; + string current_user_id = 2; +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41b2cd04..23910cdc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.13.0" kotlin = "2.2.20" -ksp = "2.2.20-2.0.3" +ksp = "2.2.20-2.0.2" # https://github.com/google/ksp/issues/2596 2.0.3 is broken coreKtx = "1.17.0" appcompat = "1.7.1" composeBom = "2025.09.00" @@ -11,7 +11,7 @@ lifecycleRuntimeKtx = "2.9.4" activityCompose = "1.11.0" androidx-media3 = "1.8.0" coil = "3.3.0" -jellyfin-sdk = "1.6.8" +jellyfin-sdk = "1.7.0-beta.6" nav3Core = "1.0.0-alpha09" lifecycleViewmodelNav3 = "1.0.0-alpha04" material3AdaptiveNav3 = "1.0.0-SNAPSHOT" @@ -21,6 +21,8 @@ kotlinx-coroutines-android = "1.10.2" kotlinx-serialization = "1.9.0" protobuf-javalite = "4.32.1" hilt = "2.57.1" +room = "2.8.0" +material3 = "1.3.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -57,13 +59,19 @@ coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } -jellyfin-sdk = { module = "org.jellyfin.sdk:jellyfin-core", version.ref = "jellyfin-sdk" } +jellyfin-core = { module = "org.jellyfin.sdk:jellyfin-core", version.ref = "jellyfin-sdk" } +jellyfin-api = { module = "org.jellyfin.sdk:jellyfin-api", version.ref = "jellyfin-sdk" } +jellyfin-api-okhttp = { module = "org.jellyfin.sdk:jellyfin-api-okhttp-jvm", version.ref = "jellyfin-sdk" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } androidx-material3-adaptive-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "material3AdaptiveNav3" } +androidx-room-common-jvm = { group = "androidx.room", name = "room-common-jvm", version.ref = "room" } +androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } +androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -73,4 +81,5 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } -hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } \ No newline at end of file +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +room = { id = "androidx.room", version.ref = "room" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 00759619..ea00d092 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,4 +27,3 @@ dependencyResolutionManagement { rootProject.name = "Dolphin" include(":app") - \ No newline at end of file