diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index f3432bcf..79d700c7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -118,31 +119,33 @@ class MainActivity : AppCompatActivity() { ) } } else { - val initialDestination = - if (server != null && user != null) { - Destination.Home() - } else { - Destination.ServerList - } - val backStack = rememberNavBackStack(initialDestination) - navigationManager.backStack = backStack - if (appPreferences.autoCheckForUpdates) { - LaunchedEffect(Unit) { - try { - updateChecker.maybeShowUpdateToast(appPreferences.updateUrl) - } catch (ex: Exception) { - Timber.w(ex, "Failed to check for update") + key(server, user) { + val initialDestination = + if (server != null && user != null) { + Destination.Home() + } else { + Destination.ServerList + } + val backStack = rememberNavBackStack(initialDestination) + navigationManager.backStack = backStack + if (appPreferences.autoCheckForUpdates) { + LaunchedEffect(Unit) { + try { + updateChecker.maybeShowUpdateToast(appPreferences.updateUrl) + } catch (ex: Exception) { + Timber.w(ex, "Failed to check for update") + } } } + ApplicationContent( + user = user, + server = server, + navigationManager = navigationManager, + preferences = preferences, + deviceProfile = deviceProfile, + modifier = Modifier.fillMaxSize(), + ) } - ApplicationContent( - user = user, - server = server, - navigationManager = navigationManager, - preferences = preferences, - deviceProfile = deviceProfile, - modifier = Modifier.fillMaxSize(), - ) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt index 3be3bbb7..5bfc8768 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt @@ -57,6 +57,7 @@ class ServerRepository apiClient.update(baseUrl = server.url, accessToken = null) _currentServer = server _currentUser = null + _currentUserDto = null } /** @@ -160,6 +161,7 @@ class ServerRepository suspend fun removeUser(user: JellyfinUser) { if (currentUser == user) { _currentUser = null + _currentUserDto = null userPreferencesDataStore.updateData { it .toBuilder() @@ -177,13 +179,17 @@ class ServerRepository suspend fun removeServer(server: JellyfinServer) { if (currentServer == server) { _currentServer = null + _currentUser = null + _currentUserDto = null userPreferencesDataStore.updateData { it .toBuilder() .apply { currentServerId = "" + currentUserId = "" }.build() } + apiClient.update(baseUrl = null, accessToken = null) } withContext(Dispatchers.IO) { serverDao.deleteServer(server.id) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 42273280..25185a83 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -30,8 +30,12 @@ import androidx.media3.common.Player import coil3.request.ErrorResult import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.data.RowColumnSaver +import com.github.damontecres.wholphin.util.ExceptionHandler import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.extensions.ticks @@ -39,6 +43,7 @@ import timber.log.Timber import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext import kotlin.math.min import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -337,3 +342,12 @@ suspend fun showToast( ) = withContext(Dispatchers.Main) { Toast.makeText(context, text, Toast.LENGTH_LONG).show() } + +/** + * Launches a coroutine with [Dispatchers.IO] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler] + */ +fun CoroutineScope.launchIO( + context: CoroutineContext = ExceptionHandler(), + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit, +): Job = launch(context = Dispatchers.IO + context, start = start, block = block) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index a38bc8b6..a03decfd 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -7,15 +7,14 @@ import com.github.damontecres.wholphin.data.JellyfinServer import com.github.damontecres.wholphin.data.JellyfinServerDao import com.github.damontecres.wholphin.data.JellyfinUser import com.github.damontecres.wholphin.data.ServerRepository +import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.NavigationManager -import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.api.client.HttpClientOptions @@ -62,7 +61,11 @@ class SwitchUserViewModel } init { - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + init() + } + + fun init() { + viewModelScope.launchIO { val allServers = serverDao .getServers() @@ -116,7 +119,7 @@ class SwitchUserViewModel } } } - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + viewModelScope.launchIO { serverRepository.currentServer?.let { val quickConnect = jellyfin @@ -140,11 +143,14 @@ class SwitchUserViewModel server: JellyfinServer, user: JellyfinUser, ) { - viewModelScope.launch(ExceptionHandler()) { + viewModelScope.launchIO { try { serverRepository.changeUser(server, user) - navigationManager.goToHome() + withContext(Dispatchers.Main) { + navigationManager.goToHome() + } } catch (ex: Exception) { + Timber.e(ex, "Error switching user") switchUserState.value = LoadingState.Error(exception = ex) } } @@ -156,7 +162,7 @@ class SwitchUserViewModel password: String, ) { quickConnectJob?.cancel() - viewModelScope.launch(ExceptionHandler()) { + viewModelScope.launchIO { try { val api = jellyfin.createApi(baseUrl = server.url) val authenticationResult by api.userApi.authenticateUserByName( @@ -168,6 +174,7 @@ class SwitchUserViewModel navigationManager.goToHome() } } catch (ex: Exception) { + Timber.e(ex, "Error logging in user") switchUserState.value = LoadingState.Error(ex) } } @@ -178,7 +185,7 @@ class SwitchUserViewModel onAuthenticated: () -> Unit, ) { quickConnectJob?.cancel() - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + viewModelScope.launchIO { try { val api = jellyfin.createApi(server.url) var state = @@ -191,7 +198,7 @@ class SwitchUserViewModel } quickConnectJob = - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + viewModelScope.launchIO { while (!state.authenticated) { delay(5_000L) state = @@ -212,6 +219,7 @@ class SwitchUserViewModel } } } catch (ex: Exception) { + Timber.e(ex, "Error during quick connect") if (ex is InvalidStatusException && ex.status == 401) { quickConnectState.value = null serverQuickConnect.value = @@ -231,7 +239,7 @@ class SwitchUserViewModel fun addServer(serverUrl: String) { addServerState.value = LoadingState.Loading - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + viewModelScope.launchIO { try { val serverInfo by jellyfin .createApi(serverUrl) @@ -279,19 +287,25 @@ class SwitchUserViewModel } fun removeUser(user: JellyfinUser) { - viewModelScope.launch(ExceptionHandler()) { + viewModelScope.launchIO { serverRepository.removeUser(user) + val serverUsers = + serverDao.getServer(user.serverId)?.users?.sortedBy { it.name } ?: listOf() + withContext(Dispatchers.Main) { + users.value = serverUsers + } } } fun removeServer(server: JellyfinServer) { - viewModelScope.launch(ExceptionHandler()) { + viewModelScope.launchIO { serverRepository.removeServer(server) + init() } } fun discoverServers() { - viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { + viewModelScope.launchIO { jellyfin.discovery.discoverLocalServers().collect { server -> val newServerList = discoveredServers.value!!