diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2e3469c..26f47ab 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
BookingPaymentsScreen(
propertyId = currentRoute.propertyId,
diff --git a/app/src/main/java/com/android/trisolarispms/data/api/GuestDocumentApi.kt b/app/src/main/java/com/android/trisolarispms/data/api/GuestDocumentApi.kt
index a965379..3b4157d 100644
--- a/app/src/main/java/com/android/trisolarispms/data/api/GuestDocumentApi.kt
+++ b/app/src/main/java/com/android/trisolarispms/data/api/GuestDocumentApi.kt
@@ -7,9 +7,11 @@ import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Multipart
+import retrofit2.http.DELETE
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
+import retrofit2.http.Query
import retrofit2.http.Streaming
interface GuestDocumentApi {
@@ -22,6 +24,15 @@ interface GuestDocumentApi {
@Part("bookingId") bookingId: RequestBody
): Response
+ @Multipart
+ @POST("properties/{propertyId}/guests/{guestId}/documents")
+ suspend fun uploadGuestDocumentWithBooking(
+ @Path("propertyId") propertyId: String,
+ @Path("guestId") guestId: String,
+ @Query("bookingId") bookingId: String,
+ @Part file: MultipartBody.Part
+ ): Response
+
@GET("properties/{propertyId}/guests/{guestId}/documents")
suspend fun listGuestDocuments(
@Path("propertyId") propertyId: String,
@@ -35,4 +46,11 @@ interface GuestDocumentApi {
@Path("guestId") guestId: String,
@Path("documentId") documentId: String
): Response
+
+ @DELETE("properties/{propertyId}/guests/{guestId}/documents/{documentId}")
+ suspend fun deleteGuestDocument(
+ @Path("propertyId") propertyId: String,
+ @Path("guestId") guestId: String,
+ @Path("documentId") documentId: String
+ ): Response
}
diff --git a/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsState.kt b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsState.kt
new file mode 100644
index 0000000..76b07b8
--- /dev/null
+++ b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsState.kt
@@ -0,0 +1,10 @@
+package com.android.trisolarispms.ui.guestdocs
+
+import com.android.trisolarispms.data.api.model.GuestDocumentDto
+
+data class GuestDocumentsState(
+ val isLoading: Boolean = false,
+ val isUploading: Boolean = false,
+ val error: String? = null,
+ val documents: List = emptyList()
+)
diff --git a/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsTab.kt b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsTab.kt
new file mode 100644
index 0000000..0518f8c
--- /dev/null
+++ b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsTab.kt
@@ -0,0 +1,341 @@
+package com.android.trisolarispms.ui.guestdocs
+
+import android.graphics.Bitmap
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Arrangement
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.background
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.ImageLoader
+import coil.compose.SubcomposeAsyncImage
+import coil.decode.SvgDecoder
+import coil.request.ImageRequest
+import com.android.trisolarispms.data.api.ApiConstants
+import com.android.trisolarispms.data.api.FirebaseAuthTokenProvider
+import com.android.trisolarispms.data.api.model.GuestDocumentDto
+import com.google.firebase.auth.FirebaseAuth
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import java.io.File
+import java.io.FileOutputStream
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.graphics.Brush
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun GuestDocumentsTab(
+ propertyId: String,
+ guestId: String,
+ bookingId: String,
+ canManageDocuments: Boolean,
+ viewModel: GuestDocumentsViewModel = viewModel()
+) {
+ val state by viewModel.state.collectAsState()
+ val context = LocalContext.current
+ val showPicker = remember { mutableStateOf(false) }
+ val auth = remember { FirebaseAuth.getInstance() }
+ val tokenProvider = remember { FirebaseAuthTokenProvider(auth) }
+ val imageLoader = remember {
+ ImageLoader.Builder(context)
+ .components { add(SvgDecoder.Factory()) }
+ .okHttpClient(
+ OkHttpClient.Builder()
+ .addInterceptor(Interceptor { chain ->
+ val original = chain.request()
+ val token = runCatching {
+ kotlinx.coroutines.runBlocking { tokenProvider.token(forceRefresh = false) }
+ }.getOrNull()
+ if (token.isNullOrBlank()) {
+ chain.proceed(original)
+ } else {
+ val request = original.newBuilder()
+ .addHeader("Authorization", "Bearer $token")
+ .build()
+ chain.proceed(request)
+ }
+ })
+ .build()
+ )
+ .build()
+ }
+
+ LaunchedEffect(propertyId, guestId) {
+ viewModel.load(propertyId, guestId)
+ }
+
+ val cameraLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicturePreview()
+ ) { bitmap: Bitmap? ->
+ if (bitmap != null) {
+ val file = writeBitmapToCache(bitmap, context.cacheDir)
+ viewModel.uploadFromFile(
+ propertyId = propertyId,
+ guestId = guestId,
+ bookingId = bookingId,
+ file = file,
+ mimeType = "image/jpeg"
+ )
+ }
+ }
+
+ val uploadLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.OpenMultipleDocuments()
+ ) { uris ->
+ if (uris.isNotEmpty()) {
+ viewModel.uploadFromUris(
+ context = context,
+ propertyId = propertyId,
+ guestId = guestId,
+ bookingId = bookingId,
+ uris = uris
+ )
+ }
+ }
+
+ Box(modifier = Modifier.fillMaxSize()) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ if (state.isLoading) {
+ CircularProgressIndicator()
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ if (state.isUploading) {
+ Text(text = "Uploading...", style = MaterialTheme.typography.bodySmall)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ state.error?.let {
+ Text(text = it, color = MaterialTheme.colorScheme.error)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ if (!canManageDocuments) {
+ Text(text = "You don't have access to view documents.")
+ return@Column
+ }
+ if (!state.isLoading && state.documents.isEmpty()) {
+ Text(text = "No documents yet")
+ }
+ val imageDocs = state.documents.filter {
+ it.contentType?.lowercase()?.startsWith("image/") == true
+ }
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ contentPadding = PaddingValues(bottom = 80.dp)
+ ) {
+ if (state.isLoading && imageDocs.isEmpty()) {
+ items(4) {
+ ShimmerTile()
+ }
+ }
+ items(imageDocs) { doc ->
+ DocumentImageTile(
+ propertyId = propertyId,
+ guestId = guestId,
+ doc = doc,
+ imageLoader = imageLoader,
+ canDelete = canManageDocuments,
+ onDelete = { documentId ->
+ viewModel.deleteDocument(propertyId, guestId, documentId)
+ }
+ )
+ }
+ }
+ }
+
+ if (canManageDocuments) {
+ FloatingActionButton(
+ onClick = { showPicker.value = true },
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(16.dp)
+ ) {
+ androidx.compose.material3.Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "Add document"
+ )
+ }
+ }
+ }
+
+ if (showPicker.value) {
+ AlertDialog(
+ onDismissRequest = { showPicker.value = false },
+ title = { Text("Add document") },
+ text = { Text("Choose camera or upload") },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ showPicker.value = false
+ cameraLauncher.launch(null)
+ }
+ ) { Text("Camera") }
+ },
+ dismissButton = {
+ TextButton(
+ onClick = {
+ showPicker.value = false
+ uploadLauncher.launch(arrayOf("*/*"))
+ }
+ ) { Text("Upload") }
+ }
+ )
+ }
+}
+
+@Composable
+private fun DocumentImageTile(
+ propertyId: String,
+ guestId: String,
+ doc: GuestDocumentDto,
+ imageLoader: ImageLoader,
+ canDelete: Boolean,
+ onDelete: (String) -> Unit
+) {
+ val context = LocalContext.current
+ val documentId = doc.id ?: return
+ val baseUrl = ApiConstants.BASE_URL.trimEnd('/')
+ val url = "$baseUrl/properties/$propertyId/guests/$guestId/documents/$documentId/file"
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(140.dp)
+ .clip(RoundedCornerShape(8.dp))
+ ) {
+ SubcomposeAsyncImage(
+ model = ImageRequest.Builder(context).data(url).build(),
+ imageLoader = imageLoader,
+ contentDescription = "Document image",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize(),
+ loading = { ShimmerOverlay() },
+ error = { ShimmerOverlay() }
+ )
+ if (canDelete) {
+ IconButton(
+ onClick = { onDelete(documentId) },
+ modifier = Modifier.align(Alignment.TopEnd)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Delete,
+ contentDescription = "Delete document",
+ tint = MaterialTheme.colorScheme.onSurface
+ )
+ }
+ }
+ val docType = doc.extractedData?.get("docType")
+ if (!docType.isNullOrBlank()) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.45f))
+ .padding(horizontal = 8.dp, vertical = 4.dp)
+ ) {
+ Text(
+ text = docType,
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.labelSmall,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ }
+ }
+}
+
+private fun writeBitmapToCache(bitmap: Bitmap, cacheDir: File): File {
+ val file = File(cacheDir, "doc_${System.currentTimeMillis()}.jpg")
+ FileOutputStream(file).use { stream ->
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 92, stream)
+ }
+ return file
+}
+
+@Composable
+private fun ShimmerTile() {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(140.dp)
+ .clip(RoundedCornerShape(8.dp))
+ ) {
+ ShimmerOverlay()
+ }
+}
+
+@Composable
+private fun ShimmerOverlay() {
+ val transition = rememberInfiniteTransition(label = "shimmer")
+ val x = transition.animateFloat(
+ initialValue = 0f,
+ targetValue = 1000f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(durationMillis = 1000, easing = LinearEasing),
+ repeatMode = RepeatMode.Restart
+ ),
+ label = "shimmerX"
+ )
+ val brush = Brush.linearGradient(
+ colors = listOf(
+ MaterialTheme.colorScheme.surfaceVariant,
+ MaterialTheme.colorScheme.onSurface.copy(alpha = 0.08f),
+ MaterialTheme.colorScheme.surfaceVariant
+ ),
+ start = androidx.compose.ui.geometry.Offset(x.value - 200f, 0f),
+ end = androidx.compose.ui.geometry.Offset(x.value, 200f)
+ )
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(brush)
+ )
+}
diff --git a/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsViewModel.kt
new file mode 100644
index 0000000..3c35113
--- /dev/null
+++ b/app/src/main/java/com/android/trisolarispms/ui/guestdocs/GuestDocumentsViewModel.kt
@@ -0,0 +1,216 @@
+package com.android.trisolarispms.ui.guestdocs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.trisolarispms.data.api.ApiClient
+import com.android.trisolarispms.data.api.model.GuestDocumentDto
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import java.io.File
+import java.io.FileOutputStream
+import java.util.Locale
+
+class GuestDocumentsViewModel : ViewModel() {
+ private val _state = MutableStateFlow(GuestDocumentsState())
+ val state: StateFlow = _state
+
+ fun load(propertyId: String, guestId: String) {
+ viewModelScope.launch {
+ _state.update { it.copy(isLoading = true, error = null) }
+ try {
+ val api = ApiClient.create()
+ val response = api.listGuestDocuments(propertyId, guestId)
+ val body = response.body()
+ if (response.isSuccessful && body != null) {
+ _state.update {
+ it.copy(
+ isLoading = false,
+ documents = body,
+ error = null
+ )
+ }
+ } else {
+ _state.update {
+ it.copy(
+ isLoading = false,
+ error = "Load failed: ${response.code()}"
+ )
+ }
+ }
+ } catch (e: Exception) {
+ _state.update {
+ it.copy(
+ isLoading = false,
+ error = e.localizedMessage ?: "Load failed"
+ )
+ }
+ }
+ }
+ }
+
+ fun uploadFromUri(
+ context: Context,
+ propertyId: String,
+ guestId: String,
+ bookingId: String,
+ uri: Uri
+ ) {
+ uploadFromUris(context, propertyId, guestId, bookingId, listOf(uri))
+ }
+
+ fun uploadFromUris(
+ context: Context,
+ propertyId: String,
+ guestId: String,
+ bookingId: String,
+ uris: List
+ ) {
+ if (uris.isEmpty()) return
+ viewModelScope.launch {
+ _state.update { it.copy(isUploading = true, error = null) }
+ var errorMessage: String? = null
+ val resolver = context.contentResolver
+ for (uri in uris) {
+ try {
+ val mime = resolver.getType(uri) ?: "application/octet-stream"
+ if (mime.lowercase(Locale.getDefault()).startsWith("video/")) {
+ errorMessage = "Video files not allowed"
+ continue
+ }
+ val filename = resolveFileName(resolver, uri) ?: "document"
+ val file = copyToCache(resolver, uri, context.cacheDir, filename)
+ uploadFile(propertyId, guestId, bookingId, file, mime)
+ } catch (e: Exception) {
+ errorMessage = e.localizedMessage ?: "Upload failed"
+ }
+ }
+ _state.update {
+ it.copy(
+ isUploading = false,
+ error = errorMessage
+ )
+ }
+ }
+ }
+
+ fun deleteDocument(propertyId: String, guestId: String, documentId: String) {
+ viewModelScope.launch {
+ _state.update { it.copy(isLoading = true, error = null) }
+ try {
+ val api = ApiClient.create()
+ val response = api.deleteGuestDocument(propertyId, guestId, documentId)
+ if (response.isSuccessful) {
+ _state.update { current ->
+ current.copy(
+ isLoading = false,
+ documents = current.documents.filterNot { it.id == documentId },
+ error = null
+ )
+ }
+ } else {
+ _state.update {
+ it.copy(
+ isLoading = false,
+ error = "Delete failed: ${response.code()}"
+ )
+ }
+ }
+ } catch (e: Exception) {
+ _state.update {
+ it.copy(
+ isLoading = false,
+ error = e.localizedMessage ?: "Delete failed"
+ )
+ }
+ }
+ }
+ }
+
+ fun uploadFromFile(
+ propertyId: String,
+ guestId: String,
+ bookingId: String,
+ file: File,
+ mimeType: String
+ ) {
+ viewModelScope.launch {
+ _state.update { it.copy(isUploading = true, error = null) }
+ try {
+ uploadFile(propertyId, guestId, bookingId, file, mimeType)
+ } catch (e: Exception) {
+ _state.update {
+ it.copy(
+ isUploading = false,
+ error = e.localizedMessage ?: "Upload failed"
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun uploadFile(
+ propertyId: String,
+ guestId: String,
+ bookingId: String,
+ file: File,
+ mimeType: String
+ ) {
+ val requestBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ val part = MultipartBody.Part.createFormData("file", file.name, requestBody)
+ val api = ApiClient.create()
+ val response = api.uploadGuestDocumentWithBooking(
+ propertyId = propertyId,
+ guestId = guestId,
+ bookingId = bookingId,
+ file = part
+ )
+ val body = response.body()
+ if (response.isSuccessful && body != null) {
+ _state.update { current ->
+ current.copy(
+ isUploading = false,
+ documents = listOf(body) + current.documents,
+ error = null
+ )
+ }
+ } else {
+ _state.update {
+ it.copy(
+ isUploading = false,
+ error = "Upload failed: ${response.code()}"
+ )
+ }
+ }
+ }
+
+ private fun resolveFileName(resolver: ContentResolver, uri: Uri): String? {
+ val cursor = resolver.query(uri, null, null, null, null) ?: return null
+ return cursor.use {
+ val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
+ if (nameIndex >= 0 && it.moveToFirst()) it.getString(nameIndex) else null
+ }
+ }
+
+ private fun copyToCache(
+ resolver: ContentResolver,
+ uri: Uri,
+ cacheDir: File,
+ filename: String
+ ): File {
+ val output = File(cacheDir, "${System.currentTimeMillis()}_$filename")
+ resolver.openInputStream(uri)?.use { input ->
+ FileOutputStream(output).use { outputStream ->
+ input.copyTo(outputStream)
+ }
+ }
+ return output
+ }
+}
diff --git a/app/src/main/java/com/android/trisolarispms/ui/roomstay/BookingDetailsTabsScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/roomstay/BookingDetailsTabsScreen.kt
index 3be1aed..7cd64ad 100644
--- a/app/src/main/java/com/android/trisolarispms/ui/roomstay/BookingDetailsTabsScreen.kt
+++ b/app/src/main/java/com/android/trisolarispms/ui/roomstay/BookingDetailsTabsScreen.kt
@@ -55,6 +55,7 @@ import coil.request.ImageRequest
import com.android.trisolarispms.data.api.ApiConstants
import com.android.trisolarispms.data.api.FirebaseAuthTokenProvider
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
+import com.android.trisolarispms.ui.guestdocs.GuestDocumentsTab
import com.google.firebase.auth.FirebaseAuth
import kotlinx.coroutines.launch
import okhttp3.Interceptor
@@ -74,10 +75,12 @@ fun BookingDetailsTabsScreen(
onEditSignature: (String) -> Unit,
onOpenPayuQr: (Long?, String?) -> Unit,
onOpenPayments: () -> Unit,
+ canManageDocuments: Boolean,
staysViewModel: BookingRoomStaysViewModel = viewModel(),
detailsViewModel: BookingDetailsViewModel = viewModel()
) {
- val pagerState = rememberPagerState(initialPage = 0, pageCount = { 2 })
+ val tabCount = if (canManageDocuments) 3 else 2
+ val pagerState = rememberPagerState(initialPage = 0, pageCount = { tabCount })
val scope = rememberCoroutineScope()
val staysState by staysViewModel.state.collectAsState()
val detailsState by detailsViewModel.state.collectAsState()
@@ -120,6 +123,15 @@ fun BookingDetailsTabsScreen(
},
text = { Text("Room Stays") }
)
+ if (canManageDocuments) {
+ Tab(
+ selected = pagerState.currentPage == 2,
+ onClick = {
+ scope.launch { pagerState.animateScrollToPage(2) }
+ },
+ text = { Text("Manage Documents") }
+ )
+ }
}
HorizontalPager(
state = pagerState,
@@ -138,6 +150,24 @@ fun BookingDetailsTabsScreen(
onOpenPayments = onOpenPayments
)
1 -> BookingRoomStaysTabContent(staysState, staysViewModel)
+ 2 -> if (canManageDocuments) {
+ val resolvedGuestId = detailsState.details?.guestId ?: guestId
+ if (!resolvedGuestId.isNullOrBlank()) {
+ GuestDocumentsTab(
+ propertyId = propertyId,
+ guestId = resolvedGuestId,
+ bookingId = bookingId,
+ canManageDocuments = canManageDocuments
+ )
+ } else {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(text = "Guest not linked yet")
+ }
+ }
+ }
}
}
}