mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Basics for user prefs, logging in, etc
This commit is contained in:
parent
763521aabf
commit
76d78246db
15 changed files with 613 additions and 35 deletions
4
.editorconfig
Normal file
4
.editorconfig
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
root = true
|
||||
|
||||
[*.kt]
|
||||
ktlint_function_naming_ignore_when_annotated_with=Composable
|
||||
|
|
@ -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,6 +107,10 @@ 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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<UserPreferences>
|
||||
|
||||
@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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<JellyfinUser>,
|
||||
)
|
||||
|
||||
@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<JellyfinServerUsers>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM servers WHERE id = :serverId")
|
||||
fun getServer(serverId: String): JellyfinServerUsers?
|
||||
}
|
||||
|
|
@ -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<JellyfinServer?>(null)
|
||||
val currentServer get() = _currentServer
|
||||
private var _currentUser by mutableStateOf<JellyfinUser?>(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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UserPreferences> =
|
||||
DataStoreFactory.create(
|
||||
serializer = userPreferencesSerializer,
|
||||
produceFile = { context.dataStoreFile("user_preferences.pb") },
|
||||
corruptionHandler =
|
||||
ReplaceFileCorruptionHandler(
|
||||
produceNewData = { UserPreferences.getDefaultInstance() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<UserPreferences> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -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(),
|
||||
) {
|
||||
}
|
||||
|
|
@ -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<UserPreferences>,
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/src/main/proto/DolphinDataStore.proto
Normal file
10
app/src/main/proto/DolphinDataStore.proto
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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" }
|
||||
|
|
@ -74,3 +82,4 @@ kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization"
|
|||
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" }
|
||||
room = { id = "androidx.room", version.ref = "room" }
|
||||
|
|
@ -27,4 +27,3 @@ dependencyResolutionManagement {
|
|||
|
||||
rootProject.name = "Dolphin"
|
||||
include(":app")
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue