diff --git a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt
index 6f17a79c..e69fd41e 100644
--- a/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt
+++ b/app/src/main/kotlin/nl/eduid/screens/accountlinked/AccountLinkedScreen.kt
@@ -58,7 +58,9 @@ private fun AccountLinkedContent(
withBackIcon = false
) {
Column(
- modifier = Modifier.verticalScroll(rememberScrollState())
+ modifier = Modifier
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState())
) {
if (errorData != null) {
AlertDialogWithSingleButton(
diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt
index b22fe87c..468277fd 100644
--- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt
+++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoRepository.kt
@@ -108,4 +108,21 @@ class PersonalInfoRepository(private val eduIdApi: EduIdApi) {
Timber.e(e, "Failed to retrieve institution name")
null
}
+
+ suspend fun getStartLinkAccount(): String? = try {
+ val response = eduIdApi.getStartLinkAccount()
+ if (response.isSuccessful) {
+ response.body()?.url
+ } else {
+ Timber.w(
+ "Failed to retrieve start link account URL: [${response.code()}/${response.message()}]${
+ response.errorBody()?.string()
+ }"
+ )
+ null
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to retrieve start link account URL")
+ null
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt
index 06fbe1f9..45f4f615 100644
--- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt
+++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoScreen.kt
@@ -1,5 +1,6 @@
package nl.eduid.screens.personalinfo
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -17,12 +18,17 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment.Companion.CenterStart
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@@ -30,6 +36,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import nl.eduid.ErrorData
import nl.eduid.R
+import nl.eduid.screens.firsttimedialog.LinkAccountContract
import nl.eduid.ui.AlertDialogWithSingleButton
import nl.eduid.ui.EduIdTopAppBar
import nl.eduid.ui.InfoTab
@@ -43,11 +50,30 @@ fun PersonalInfoScreen(
viewModel: PersonalInfoViewModel,
onEmailClicked: () -> Unit,
onManageAccountClicked: (dateString: String) -> Unit,
+ goToAccountLinked: () -> Unit = {},
goBack: () -> Unit,
) = EduIdTopAppBar(
onBackClicked = goBack,
) {
val uiState by viewModel.uiState.observeAsState(UiState())
+ var isGettingLinkUrl by rememberSaveable { mutableStateOf(false) }
+ var isLinkingStarted by rememberSaveable { mutableStateOf(false) }
+ val launcher =
+ rememberLauncherForActivityResult(contract = LinkAccountContract(), onResult = { _ ->
+ if (isLinkingStarted) {
+ isLinkingStarted = false
+ goToAccountLinked()
+ }
+ })
+
+ if (isGettingLinkUrl && uiState.haveValidLinkIntent()) {
+ LaunchedEffect(key1 = viewModel) {
+ isGettingLinkUrl = false
+ launcher.launch(uiState.linkUrl)
+ isLinkingStarted = true
+ }
+ }
+
PersonalInfoScreenContent(
personalInfo = uiState.personalInfo,
isLoading = uiState.isLoading,
@@ -56,6 +82,10 @@ fun PersonalInfoScreen(
onEmailClicked = onEmailClicked,
removeConnection = { index -> viewModel.removeConnection(index) },
onManageAccountClicked = onManageAccountClicked,
+ addLinkToAccount = {
+ isGettingLinkUrl = true
+ viewModel.requestLinkUrl()
+ },
)
}
@@ -68,10 +98,10 @@ fun PersonalInfoScreenContent(
onEmailClicked: () -> Unit = {},
removeConnection: (Int) -> Unit = {},
onManageAccountClicked: (dateString: String) -> Unit = {},
+ addLinkToAccount: () -> Unit = {},
) = Column(
verticalArrangement = Arrangement.Bottom,
- modifier = Modifier
- .verticalScroll(rememberScrollState())
+ modifier = Modifier.verticalScroll(rememberScrollState())
) {
if (errorData != null) {
AlertDialogWithSingleButton(
@@ -86,9 +116,7 @@ fun PersonalInfoScreenContent(
Text(
style = MaterialTheme.typography.titleLarge.copy(
textAlign = TextAlign.Start, color = ButtonGreen
- ),
- text = stringResource(R.string.personal_info_title),
- modifier = Modifier.fillMaxWidth()
+ ), text = stringResource(R.string.personal_info_title), modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(12.dp))
Text(
@@ -151,6 +179,38 @@ fun PersonalInfoScreenContent(
)
}
+ Spacer(Modifier.height(12.dp))
+ OutlinedButton(
+ onClick = addLinkToAccount,
+ shape = RoundedCornerShape(CornerSize(6.dp)),
+ modifier = Modifier
+ .fillMaxWidth()
+ .sizeIn(minHeight = 72.dp)
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = stringResource(R.string.personalinfo_add_role_institution),
+ style = MaterialTheme.typography.bodyLarge.copy(
+ textAlign = TextAlign.Start,
+ color = ButtonTextGrey,
+ fontWeight = FontWeight.Bold,
+ )
+ )
+ Text(
+ text = stringResource(R.string.personalinfo_add_via),
+ style = MaterialTheme.typography.bodyMedium.copy(
+ textAlign = TextAlign.Start,
+ color = ButtonTextGrey,
+ fontWeight = FontWeight.Light,
+ fontStyle = FontStyle.Italic
+ )
+ )
+ }
+ Image(
+ painter = painterResource(R.drawable.ic_plus),
+ contentDescription = "",
+ )
+ }
Spacer(Modifier.height(42.dp))
OutlinedButton(
onClick = { onManageAccountClicked(personalInfo.dateCreated.getDateTimeString("EEEE, dd MMMM yyyy 'at' HH:MM")) },
@@ -163,8 +223,7 @@ fun PersonalInfoScreenContent(
painter = painterResource(R.drawable.cog_icon),
alignment = CenterStart,
contentDescription = "",
- modifier = Modifier
- .padding(end = 48.dp)
+ modifier = Modifier.padding(start = 24.dp, end = 24.dp)
)
Text(
text = stringResource(R.string.personalinfo_manage_your_account),
diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt
index 203e767d..0d9d7c47 100644
--- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt
+++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/PersonalInfoViewModel.kt
@@ -1,5 +1,7 @@
package nl.eduid.screens.personalinfo
+import android.content.Intent
+import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -7,6 +9,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import nl.eduid.ErrorData
import nl.eduid.di.model.UserDetails
+import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@@ -82,6 +85,46 @@ class PersonalInfoViewModel @Inject constructor(private val repository: Personal
)
}
+ fun requestLinkUrl() = viewModelScope.launch {
+ val currentUiState = uiState.value ?: UiState()
+ uiState.postValue(currentUiState.copy(isLoading = true, linkUrl = null))
+ try {
+ val response = repository.getStartLinkAccount()
+ if (response != null) {
+ uiState.postValue(
+ currentUiState.copy(
+ linkUrl = createLaunchIntent(response), isLoading = false
+ )
+ )
+ } else {
+ uiState.postValue(
+ currentUiState.copy(
+ isLoading = false, errorData = ErrorData(
+ "Failed to get link URL",
+ "Could not retrieve URL to link your current account"
+ )
+ )
+ )
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to get link account for current user")
+ uiState.postValue(
+ currentUiState.copy(
+ isLoading = false, errorData = ErrorData(
+ "Failed to get link URL",
+ "Could not retrieve URL to link your current account"
+ )
+ )
+ )
+ }
+ }
+
+ private fun createLaunchIntent(url: String): Intent {
+ val intent = Intent(Intent.ACTION_VIEW)
+ intent.data = Uri.parse(url)
+ return intent
+ }
+
private fun convertToUiData(userDetails: UserDetails): PersonalInfo {
val dateCreated = userDetails.created
val linkedAccounts = userDetails.linkedAccounts
diff --git a/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt b/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt
index 712fc130..ec4965c5 100644
--- a/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt
+++ b/app/src/main/kotlin/nl/eduid/screens/personalinfo/UiState.kt
@@ -1,9 +1,13 @@
package nl.eduid.screens.personalinfo
+import android.content.Intent
import nl.eduid.ErrorData
data class UiState(
val personalInfo: PersonalInfo = PersonalInfo(),
+ val linkUrl: Intent? = null,
val isLoading: Boolean = false,
val errorData: ErrorData? = null,
-)
\ No newline at end of file
+) {
+ fun haveValidLinkIntent() = !isLoading && errorData == null && linkUrl != null
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/plus_icon_gray.xml b/app/src/main/res/drawable/ic_plus.xml
similarity index 100%
rename from app/src/main/res/drawable/plus_icon_gray.xml
rename to app/src/main/res/drawable/ic_plus.xml
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d9a2592c..d10bc2ff 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -203,6 +203,8 @@
At %s
Role & institution
Manage your account
+ Add role & institution
+ Proceed to add this via SURFConext
Delete login details *
Delete service