Enhance Google OAuth functionality in Profile view. Implement linking and unlinking of Google accounts with corresponding UI updates. Add loading states and feedback messages. Update mobile app to support OAuth identity management and integrate new API endpoints for fetching and unlinking identities. Increment version code to 5 and update version name to 0.8.0-alpha4.
This commit is contained in:
@@ -23,8 +23,8 @@ android {
|
||||
applicationId = "de.tsschulz.timeclock"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 4
|
||||
versionName = "0.8.0-alpha3"
|
||||
versionCode = 5
|
||||
versionName = "0.8.0-alpha4"
|
||||
buildConfigField("String", "API_BASE_URL", "\"${apiBaseUrl.replace("\\", "\\\\").replace("\"", "\\\"")}\"")
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,20 @@ data class PasswordChangeRequest(
|
||||
val confirmPassword: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OAuthIdentitiesResponse(
|
||||
val success: Boolean = false,
|
||||
val identities: List<OAuthIdentityDto> = emptyList(),
|
||||
val error: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OAuthIdentityDto(
|
||||
val provider: String,
|
||||
val identity: String? = null,
|
||||
val id: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TimewishDto(
|
||||
val id: String,
|
||||
|
||||
@@ -80,6 +80,14 @@ class TimeClockApiClient(
|
||||
return decode(OAuthLinkUrlResponse.serializer(), raw)
|
||||
}
|
||||
|
||||
suspend fun getOAuthIdentities(): OAuthIdentitiesResponse =
|
||||
decode(OAuthIdentitiesResponse.serializer(), execute(authorized("auth/identities").get().build()))
|
||||
|
||||
suspend fun unlinkOAuthProvider(provider: String): MessageResponse {
|
||||
val raw = execute(authorized("auth/identity/$provider").delete().build())
|
||||
return decode(MessageResponse.serializer(), raw.ifBlank { "{}" })
|
||||
}
|
||||
|
||||
suspend fun linkExistingOAuthAccount(pendingToken: String, email: String, password: String): LoginResponse {
|
||||
val raw = execute(
|
||||
Request.Builder()
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.tsschulz.timeclock.data.settings
|
||||
|
||||
import de.tsschulz.timeclock.data.api.InvitationDto
|
||||
import de.tsschulz.timeclock.data.api.InviteRequest
|
||||
import de.tsschulz.timeclock.data.api.OAuthIdentityDto
|
||||
import de.tsschulz.timeclock.data.api.PasswordChangeRequest
|
||||
import de.tsschulz.timeclock.data.api.ProfileDto
|
||||
import de.tsschulz.timeclock.data.api.ProfileUpdateRequest
|
||||
@@ -28,6 +29,10 @@ class SettingsRepository(
|
||||
return response.url
|
||||
}
|
||||
|
||||
suspend fun getOAuthIdentities(): List<OAuthIdentityDto> = api.getOAuthIdentities().identities
|
||||
|
||||
suspend fun unlinkOAuthProvider(provider: String) = api.unlinkOAuthProvider(provider)
|
||||
|
||||
suspend fun changePassword(oldPassword: String, newPassword: String, confirmPassword: String) =
|
||||
api.changePassword(PasswordChangeRequest(oldPassword, newPassword, confirmPassword))
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ private fun DemoScreen(
|
||||
settingsViewModel.updateProfile(name, stateId, weekWorkdays, dailyHours, titleType)
|
||||
},
|
||||
onLinkGoogle = { settingsViewModel.startGoogleLink() },
|
||||
onUnlinkGoogle = { settingsViewModel.unlinkGoogle() },
|
||||
)
|
||||
}
|
||||
AppRoute.Password -> PasswordScreen(
|
||||
|
||||
@@ -50,6 +50,7 @@ fun ProfileScreen(
|
||||
isTablet: Boolean,
|
||||
onSave: (String, String?, Int, Double, Int) -> Unit,
|
||||
onLinkGoogle: () -> Unit,
|
||||
onUnlinkGoogle: () -> Unit,
|
||||
) {
|
||||
val profile = state.profile
|
||||
var fullName by rememberSaveable { mutableStateOf("") }
|
||||
@@ -82,7 +83,18 @@ fun ProfileScreen(
|
||||
TcTextField("Stunden pro Tag", dailyHours, { dailyHours = it }, placeholder = "8.0")
|
||||
TitleTypeDropdown(preferredTitleType, { preferredTitleType = it })
|
||||
FieldLabel("Google-Anmeldung")
|
||||
TcButton("Mit Google-Konto verknüpfen", variant = ButtonVariant.Default, onClick = onLinkGoogle)
|
||||
val googleLinked = state.oauthIdentities.any { it.provider == "google" }
|
||||
Text(
|
||||
text = if (googleLinked) "Google-Konto ist verknüpft" else "Kein Google-Konto verknüpft",
|
||||
color = if (googleLinked) TcColors.Success else TcColors.TextMuted,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
if (googleLinked) {
|
||||
TcButton("Google-Verknüpfung entfernen", variant = ButtonVariant.Danger, onClick = onUnlinkGoogle)
|
||||
} else {
|
||||
TcButton("Mit Google-Konto verknüpfen", variant = ButtonVariant.Default, onClick = onLinkGoogle)
|
||||
}
|
||||
TcButton("Speichern", variant = ButtonVariant.Primary, onClick = {
|
||||
onSave(
|
||||
fullName,
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.tsschulz.timeclock.data.api.InvitationDto
|
||||
import de.tsschulz.timeclock.data.api.OAuthIdentityDto
|
||||
import de.tsschulz.timeclock.data.api.ProfileDto
|
||||
import de.tsschulz.timeclock.data.api.StateDto
|
||||
import de.tsschulz.timeclock.data.api.TimeClockApiClient
|
||||
@@ -28,6 +29,7 @@ data class SettingsUiState(
|
||||
val invites: List<InvitationDto> = emptyList(),
|
||||
val watchers: List<WatcherDto> = emptyList(),
|
||||
val googleLinkUrl: String? = null,
|
||||
val oauthIdentities: List<OAuthIdentityDto> = emptyList(),
|
||||
)
|
||||
|
||||
class SettingsViewModel(
|
||||
@@ -41,7 +43,11 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
fun loadProfile() = launchLoad {
|
||||
copy(profile = repository.getProfile(), states = repository.getStates())
|
||||
copy(
|
||||
profile = repository.getProfile(),
|
||||
states = repository.getStates(),
|
||||
oauthIdentities = repository.getOAuthIdentities(),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateProfile(fullName: String, stateId: String?, weekWorkdays: Int, dailyHours: Double, preferredTitleType: Int) = launchMutation("Profil gespeichert") {
|
||||
@@ -62,6 +68,11 @@ class SettingsViewModel(
|
||||
_uiState.update { it.copy(googleLinkUrl = null) }
|
||||
}
|
||||
|
||||
fun unlinkGoogle() = launchMutation("Google-Verknüpfung entfernt") {
|
||||
repository.unlinkOAuthProvider("google")
|
||||
loadProfile()
|
||||
}
|
||||
|
||||
fun changePassword(oldPassword: String, newPassword: String, confirmPassword: String) = launchMutation("Passwort geändert") {
|
||||
repository.changePassword(oldPassword, newPassword, confirmPassword)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user