Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import com.uni.eventbridge.data.mapper.toDto
import com.uni.eventbridge.data.remote.dto.CategoryDto
import com.uni.eventbridge.data.remote.dto.EventDto
import com.uni.eventbridge.data.remote.dto.MembershipInsert
import com.uni.eventbridge.data.remote.dto.ProfileDto
import com.uni.eventbridge.data.remote.dto.toDomain
import com.uni.eventbridge.data.remote.supabase
import com.uni.eventbridge.domain.entity.Category
import com.uni.eventbridge.domain.entity.Event
import com.uni.eventbridge.domain.entity.User
import com.uni.eventbridge.domain.model.CreateEventDraft
import com.uni.eventbridge.domain.repository.EventRepository
import io.github.aakira.napier.Napier
Expand All @@ -20,18 +23,18 @@ import kotlin.time.Clock
class SupabaseEventRepository : EventRepository {

override suspend fun getCategory(): List<Category> {
val result = supabase.postgrest["categories"].select()
Napier.d (tag = "CategoryProblem", message = "Raw result from Supabase: ${result.data}")

return result.decodeList<CategoryDto>().map { it.toDomain() }
return supabase.postgrest
.rpc("get_categories")
.decodeList<CategoryDto>()
.map { it.toDomain() }
}

override suspend fun getEventDetailsById(eventId: Long): Event =
supabase.postgrest.rpc(
"get_event_by_id",
mapOf("p_event_id" to eventId)
).decodeList<EventDto>()
.first ()
.first()
.toDomain()

override suspend fun getEventByCategory(categoryId: Long?): List<Event> =
Expand All @@ -44,7 +47,7 @@ class SupabaseEventRepository : EventRepository {
searchEvent(query)

override suspend fun searchEvent(query: String): List<Event> {
return supabase.postgrest.rpc(
return supabase.postgrest.rpc(
"search_events",
mapOf("query" to query)
).decodeList<EventDto>().map { it.toDomain() }
Expand All @@ -58,7 +61,6 @@ class SupabaseEventRepository : EventRepository {
try {
supabase.postgrest["memberships"]
.insert(MembershipInsert(eventId, userId))
Napier.d(tag = "joinEvent", message = "Insert completed successfully")
} catch (e: Exception) {
Napier.e(tag = "joinEvent", message = "Insert failed: ${e.message}")
}
Expand Down Expand Up @@ -114,4 +116,14 @@ class SupabaseEventRepository : EventRepository {
val dto = request.toDto(bannerUrl)
supabase.postgrest["events"].insert(dto)
}

override suspend fun getEventAttendance(eventId: Long): List<User> {
val x = supabase.postgrest.rpc(
"get_event_attendees",
mapOf("p_event_id" to eventId)
).decodeList<ProfileDto>().map { it.toDomain() }
Napier.d(tag = "getEventAttendees", message = "Raw result from Supabase: ${x}")

return x
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.uni.eventbridge.domain.repository

import com.uni.eventbridge.domain.entity.Category
import com.uni.eventbridge.domain.entity.Event
import com.uni.eventbridge.domain.entity.User
import com.uni.eventbridge.domain.model.CreateEventDraft

interface EventRepository {
Expand All @@ -20,6 +21,7 @@ interface EventRepository {
suspend fun createEvent(request: CreateEventDraft)
suspend fun isUserJoined(eventId: Long): Boolean
suspend fun searchEvent(query: String): List<Event>
suspend fun getEventAttendance(eventId: Long): List<User>

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ fun EventCard(
.padding(start = 16.dp, top = 13.dp)
.align(Alignment.Top)
) {
state?.let {
state?.takeIf { it.isNotBlank() }?.let {
Text(
text = state,
text = it,
color = White,
modifier = modifier
.noRippleClickable { onClick() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.uni.eventbridge.presentation.common.component.EventBridgeScaffold
import com.uni.eventbridge.presentation.common.component.theme.White
import com.uni.eventbridge.presentation.eventDetails.component.AboutSection
import com.uni.eventbridge.presentation.eventDetails.component.AttendanceDialog
import com.uni.eventbridge.presentation.eventDetails.component.AttendeesRow
import com.uni.eventbridge.presentation.eventDetails.component.EventHeader
import com.uni.eventbridge.presentation.eventDetails.component.EventInfoRows
import com.uni.eventbridge.presentation.eventDetails.component.HeroSection
Expand Down Expand Up @@ -69,7 +71,9 @@ fun EventDetailsScreen(
onBackClick = viewModel::onBackClicked,
onJoinClick = viewModel::onJoinEventClick,
onLeaveClick = viewModel::onLeaveEventClick,
onNavigateClick = viewModel::onNavigateClick
onNavigateClick = viewModel::onNavigateClick,
onAttendeesClick = viewModel::onAttendeesClick,
onDismissAttendanceDialog = viewModel::onDismissAttendanceDialog
)
}

Expand All @@ -81,8 +85,16 @@ fun EventDetailsContent(
onJoinClick: () -> Unit = {},
onLeaveClick: () -> Unit = {},
onNavigateClick: () -> Unit = {},
onAttendeesClick: () -> Unit = {},
onDismissAttendanceDialog: () -> Unit = {},
) {
Box(modifier = Modifier.fillMaxSize()) {
if (uiState.showAttendanceDialog) {
AttendanceDialog(
attendees = uiState.attendees,
onDismiss = onDismissAttendanceDialog
)
}
EventBridgeScaffold(
isLoading = uiState.isLoading,
bottomBar = {
Expand Down Expand Up @@ -147,6 +159,10 @@ fun EventDetailsContent(
}

AboutSection(description = uiState.description.orEmpty())
AttendeesRow(
avatarUrls = uiState.attendanceAvatarUrls,
onClick = onAttendeesClick
)

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ data class EventDetailsUiState(
val pinLongitude: Double? = null,
val userLocation: Location? = null,
val route: MapRoute? = null,
val attendees: List<com.uni.eventbridge.domain.entity.User> = emptyList(),
val showAttendanceDialog: Boolean = false,
val attendanceAvatarUrls: List<String> = emptyList(),
) {
data class CategoryUiState(
val id: Long = 0L,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,36 @@ class EventDetailsViewModel(
) {

init {
loadEventDetails()
loadEventData()
}

private fun loadEventDetails() {
private fun loadEventData() {
tryToExecute(
callee = {
val event = eventRepository.getEventDetailsById(eventId)
val joined = eventRepository.isUserJoined(eventId)
Pair(event, joined)
val attendees = eventRepository.getEventAttendance(eventId)
Triple(event, joined, attendees)
},
onStart = {
updateState { it.copy(isLoading = true) }
},
onSuccess = { (event, joined) ->
updateState {
onSuccess = { (event, joined, attendees) ->
updateState {
event.toUiState().copy(
isLoading = false,
isRegistered = joined,
attendees = attendees,
attendanceAvatarUrls = attendees.map { it.avatarUrl },
isLoading = false
)
}
},
onError = { throwable ->
onError = {
updateState { it.copy(isLoading = false) }
sendEffect(EventDetailsUIEffect.ShowErrorSnackBar("Failed to load event details"))
sendEffect(EventDetailsUIEffect.ShowErrorSnackBar("Failed to load event"))
}
)
}

fun onJoinEventClick() {
val state = currentState
if (state.isFull) {
Expand Down Expand Up @@ -98,4 +100,12 @@ class EventDetailsViewModel(
sendEffect(EventDetailsUIEffect.NavigateBack)
}

fun onAttendeesClick() {
updateState { it.copy(showAttendanceDialog = true) }
}

fun onDismissAttendanceDialog() {
updateState { it.copy(showAttendanceDialog = false) }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.uni.eventbridge.presentation.eventDetails.component

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.uni.eventbridge.domain.entity.User
import com.uni.eventbridge.presentation.common.component.theme.SurfaceDark
import com.uni.eventbridge.presentation.common.component.theme.White

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AttendanceDialog(
attendees: List<User>,
onDismiss: () -> Unit
) {
BasicAlertDialog(
onDismissRequest = onDismiss,
modifier = Modifier
.fillMaxWidth(0.9f)
.fillMaxHeight(0.6f)
) {
Surface(
shape = RoundedCornerShape(24.dp),
color = White,
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.padding(24.dp)
) {
Text(
text = "Attendees",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
color = SurfaceDark,
modifier = Modifier.padding(bottom = 16.dp)
)

HorizontalDivider(
modifier = Modifier.padding(bottom = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant
)

LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.weight(1f)
) {
items(attendees) { attendee ->
AttendeeItem(attendee)
}
}
}
}
}
}

@Composable
private fun AttendeeItem(attendee: User) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()
) {
AsyncImage(
model = attendee.avatarUrl,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.LightGray)
)

Column {
Text(
text = attendee.fullName,
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
color = SurfaceDark
)
Text(
text = attendee.email,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
Loading
Loading