Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dependencies {
implementation(project(":feature:artist"))
implementation(project(":feature:album"))
implementation(project(":feature:search"))
implementation(project(":feature:settings"))
// Common Feature
implementation(project(":feature:common"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.android.swingmusic.home.presentation.destinations.HomeDestination
import com.android.swingmusic.player.presentation.screen.destinations.LyricsScreenDestination
import com.android.swingmusic.player.presentation.screen.destinations.QueueScreenDestination
import com.android.swingmusic.search.presentation.screen.destinations.ViewAllSearchResultsDestination
import com.android.swingmusic.settings.presentation.screen.destinations.SettingsScreenDestination
import com.ramcosta.composedestinations.navigation.navigate

class CoreNavigator(
Expand Down Expand Up @@ -152,4 +153,10 @@ class CoreNavigator(
launchSingleTop = true
}
}

override fun gotoSettings() {
navController.navigate(SettingsScreenDestination) {
launchSingleTop = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.android.swingmusic.player.presentation.screen.destinations.NowPlaying
import com.android.swingmusic.player.presentation.screen.destinations.QueueScreenDestination
import com.android.swingmusic.search.presentation.screen.destinations.SearchScreenDestination
import com.android.swingmusic.search.presentation.screen.destinations.ViewAllSearchResultsDestination
import com.android.swingmusic.settings.presentation.screen.destinations.SettingsScreenDestination
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.ramcosta.composedestinations.spec.NavGraphSpec
import com.ramcosta.composedestinations.spec.Route
Expand Down Expand Up @@ -49,6 +50,7 @@ object NavGraphs {
ViewAllScreenOnArtistDestination,
ArtistInfoScreenDestination,
ViewAllSearchResultsDestination,
SettingsScreenDestination,
)

return (preAuthDestSpec + pastAuthDestSpec).associateBy { it.route }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ class AuthTokensDataStore @Inject constructor(
data[MAX_AGE] = maxAge
}
}

suspend fun clear() {
context.dataStore.edit { it.clear() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,21 @@ class DataAuthRepository @Inject constructor(
override suspend fun getBaseUrl(): String? {
if (BaseUrlHolder.baseUrl == null) {
BaseUrlHolder.baseUrl = withContext(Dispatchers.IO) {
baseUrlDao.getBaseUrl()?.toModel()?.url
baseUrlDao.getBaseUrl()?.toModel()?.url?.normalizeBaseUrl()
}
}
return BaseUrlHolder.baseUrl
}

override suspend fun storeBaseUrl(url: String) {
baseUrlDao.insertBaseUrl(BaseUrl(url = "$url/").toEntity()) // BASE_URL must end with '/'
val normalized = url.normalizeBaseUrl()
baseUrlDao.insertBaseUrl(BaseUrl(url = normalized).toEntity())
BaseUrlHolder.baseUrl = normalized
}

// BASE_URL must end with a single '/'. Idempotent against legacy values with 0, 1, or many trailing slashes.
private fun String.normalizeBaseUrl(): String = trimEnd('/') + "/"

override suspend fun getAccessToken(): String? {
if (AuthTokenHolder.accessToken == null) {
AuthTokenHolder.accessToken = authTokensDataStore.accessToken.firstOrNull()
Expand Down Expand Up @@ -84,8 +89,14 @@ class DataAuthRepository @Inject constructor(
val refreshToken = getRefreshToken()
val baseUrl = BaseUrlHolder.baseUrl ?: getBaseUrl()

if (baseUrl.isNullOrBlank() || refreshToken.isNullOrBlank()) {
// Not signed in. Skip the refresh attempt so we don't fire requests
// like http://default/null/auth/refresh while between sessions.
return null
}

val result = authApiService.refreshTokens(
url = "$baseUrl/auth/refresh",
url = "${baseUrl.trimEnd('/')}/auth/refresh",
bearerRefreshToken = "Bearer $refreshToken"
)

Expand Down Expand Up @@ -219,4 +230,13 @@ class DataAuthRepository @Inject constructor(
}
}
}

override suspend fun signOut() {
authTokensDataStore.clear()
baseUrlDao.clearBaseUrl()
userDao.clearLoggedInUser()
AuthTokenHolder.accessToken = null
AuthTokenHolder.refreshToken = null
BaseUrlHolder.baseUrl = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ interface AuthRepository {
fun processQrCodeData(encoded: String): Pair<String, String>

suspend fun logInWithQrCode(url: String, pairCode: String): Flow<Resource<LogInResult>>

suspend fun signOut()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ interface BaseUrlDao {
suspend fun getBaseUrl(): BaseUrlEntity?

@Query("DELETE FROM base_url")
fun clearBaseUrl()
suspend fun clearBaseUrl()
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ interface UserDao {

@Query("SELECT * FROM logged_in_user LIMIT 1")
suspend fun getLoggedInUser(): UserEntity?

@Query("DELETE FROM logged_in_user")
suspend fun clearLoggedInUser()
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ interface CommonNavigator {

fun gotoLyrics()

fun gotoSettings()

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ class DataFolderRepository @Inject constructor(
val accessToken = AuthTokenHolder.accessToken ?: authRepository.getAccessToken()
val baseUrl = BaseUrlHolder.baseUrl ?: authRepository.getBaseUrl()

if (baseUrl.isNullOrBlank()) {
// Not signed in yet (or just signed out). Returning an empty flow avoids
// capturing a null base into the PagingSource and firing requests to
// urls like http://default/nullfolder.
return emptyFlow()
}

return Pager(
config = PagingConfig(enablePlaceholders = false, pageSize = 20, prefetchDistance = 1),
pagingSourceFactory = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
Expand All @@ -18,10 +19,12 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
Expand Down Expand Up @@ -96,7 +99,8 @@ private fun FoldersAndTracks(
isManualRefreshing: Boolean,
onManualRefreshingChange: (Boolean) -> Unit,
getSavedScroll: (path: String) -> Pair<Int, Int>?,
onSaveScroll: (path: String, index: Int, offset: Int) -> Unit
onSaveScroll: (path: String, index: Int, offset: Int) -> Unit,
onClickSettings: () -> Unit
) {
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -190,15 +194,29 @@ private fun FoldersAndTracks(
Scaffold(
modifier = Modifier.padding(it),
topBar = {
Text(
modifier = Modifier.padding(
top = 16.dp,
start = 16.dp,
bottom = 8.dp
),
text = "Folders",
style = MaterialTheme.typography.headlineMedium
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
top = 16.dp,
start = 16.dp,
end = 8.dp,
bottom = 8.dp
),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = "Folders",
style = MaterialTheme.typography.headlineMedium
)
IconButton(onClick = onClickSettings) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = "Settings"
)
}
}
}
) { paddingValues ->
Surface(
Expand Down Expand Up @@ -690,7 +708,8 @@ fun FoldersAndTracksScreen(
getSavedScroll = { path -> foldersViewModel.getScrollPosition(path) },
onSaveScroll = { path, index, offset ->
foldersViewModel.saveScrollPosition(path, index, offset)
}
},
onClickSettings = { navigator.gotoSettings() }
)
}
}
4 changes: 4 additions & 0 deletions feature/settings/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
// Project Core
implementation(project(":core"))
implementation(project(":auth"))
implementation(project(":database"))
implementation(project(":uicomponent"))
implementation(project(":feature:common"))


// Compose
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.android.swingmusic.settings.presentation.event

internal sealed class SettingsUiEffect {
data object NavigateBack : SettingsUiEffect()
data object NavigateToQrScan : SettingsUiEffect()
data class ShowSnackBar(val message: String) : SettingsUiEffect()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.android.swingmusic.settings.presentation.event

internal sealed interface SettingsUiEvent {
data object OnBackPressed : SettingsUiEvent
data object OnClickRePairDevice : SettingsUiEvent
data class OnToggleUseLyricsPlugin(val enabled: Boolean) : SettingsUiEvent
data class OnToggleLyricsAutoDownload(val enabled: Boolean) : SettingsUiEvent
data class OnToggleLyricsOverrideUnsynced(val enabled: Boolean) : SettingsUiEvent
}
Loading