From 1f602dd17ca784abc0c479c08dba65922892b9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Tissi=C3=A8res?= Date: Wed, 29 May 2024 17:11:13 +0200 Subject: [PATCH 1/2] SearchMenuFiltersTest + SearchMenuSheetTest : correcting initial value sortBy --- .../github/swent/echo/compose/components/SearchMenuSheetTest.kt | 2 +- .../echo/compose/components/searchmenu/SearchMenuFiltersTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/com/github/swent/echo/compose/components/SearchMenuSheetTest.kt b/app/src/test/java/com/github/swent/echo/compose/components/SearchMenuSheetTest.kt index ebd29ebd2..03da58885 100644 --- a/app/src/test/java/com/github/swent/echo/compose/components/SearchMenuSheetTest.kt +++ b/app/src/test/java/com/github/swent/echo/compose/components/SearchMenuSheetTest.kt @@ -35,7 +35,7 @@ class SearchMenuSheetTest { fullChecked = true, from = 0f, to = 14f, - sortBy = SortBy.NONE + sortBy = SortBy.DATE_ASC ) private var callback = 0 diff --git a/app/src/test/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFiltersTest.kt b/app/src/test/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFiltersTest.kt index 8f52de5c2..4db1b472e 100644 --- a/app/src/test/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFiltersTest.kt +++ b/app/src/test/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFiltersTest.kt @@ -29,7 +29,7 @@ class SearchMenuFiltersTest { fullChecked = true, from = 0f, to = 14f, - sortBy = SortBy.NONE + sortBy = SortBy.DATE_ASC ) private var checkboxes = listOf("EPFL", "Section", "Class", "Pending", "Confirmed", "Full") private var callback = 0 From 3f50b2575b67c209c4e0c6a316e6ec6b1d20652c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Tissi=C3=A8res?= Date: Thu, 30 May 2024 14:47:24 +0200 Subject: [PATCH 2/2] Commented code --- .../compose/association/AssociationDetails.kt | 40 ++++++++--- .../association/AssociationListScreen.kt | 48 +++++++++----- .../compose/association/AssociationScreen.kt | 26 +++++++- .../swent/echo/compose/components/Dropdown.kt | 25 +++++-- .../components/HamburgerMenuDrawerSheet.kt | 19 ------ .../echo/compose/components/HomeScreen.kt | 1 + .../echo/compose/components/Hypertext.kt | 10 +++ .../compose/components/JoinEventButton.kt | 14 +++- .../echo/compose/components/ListDrawer.kt | 22 ++++++- .../swent/echo/compose/components/Pager.kt | 11 ++++ .../echo/compose/components/SearchBar.kt | 12 ++++ .../compose/components/SearchMenuSheet.kt | 16 +++-- .../echo/compose/components/TopAppBar.kt | 9 ++- .../components/searchmenu/FiltersContainer.kt | 21 +++--- .../searchmenu/SearchMenuDiscover.kt | 19 ++++-- .../searchmenu/SearchMenuFilters.kt | 66 ++++++++++++++----- .../echo/compose/myevents/MyEventsScreen.kt | 20 +++++- .../association/AssociationViewModel.kt | 24 +++++-- .../viewmodels/myevents/MyEventsViewModel.kt | 14 +++- 19 files changed, 315 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/github/swent/echo/compose/association/AssociationDetails.kt b/app/src/main/java/com/github/swent/echo/compose/association/AssociationDetails.kt index 0e3854547..1cf85a8ce 100644 --- a/app/src/main/java/com/github/swent/echo/compose/association/AssociationDetails.kt +++ b/app/src/main/java/com/github/swent/echo/compose/association/AssociationDetails.kt @@ -37,32 +37,39 @@ import com.github.swent.echo.data.model.Association import com.github.swent.echo.data.model.Event import com.github.swent.echo.data.model.Tag +// This Composable function displays the details of an association. @Composable fun AssociationDetails( - association: Association, - isFollowed: Boolean, - follow: (Association) -> Unit, - events: List, - isOnline: Boolean, - refreshEvents: () -> Unit, - onTagPressed: (Tag) -> Unit = {}, + association: Association, // The association to display + isFollowed: Boolean, // Whether the association is followed by the user + follow: (Association) -> Unit, // Function to follow/unfollow the association + events: List, // List of events related to the association + isOnline: Boolean, // Whether the user is online + refreshEvents: () -> Unit, // Function to refresh the list of events + onTagPressed: (Tag) -> Unit = {}, // Function to handle tag press events ) { + // Define layout parameters val paddingValues = 10.dp val phoneHorizontalCenter = (LocalConfiguration.current.screenWidthDp / 2).dp val followWidth = 150.dp val followHeight = 40.dp val followSpaceInside = 5.dp val verticalSpace = 12.dp + + // Start of the layout Column( modifier = Modifier.fillMaxSize().padding(paddingValues).testTag("association_details") ) { + // Association name and follow/unfollow button Box(modifier = Modifier.fillMaxWidth()) { + // Association name Text( association.name, modifier = Modifier.align(Alignment.CenterStart).widthIn(max = phoneHorizontalCenter), style = MaterialTheme.typography.titleLarge ) + // Follow/unfollow button Button( enabled = isOnline, onClick = { follow(association) }, @@ -72,11 +79,13 @@ fun AssociationDetails( .height(followHeight) .testTag("association_details_follow_button") ) { + // Icon for follow/unfollow Icon( if (isFollowed) Icons.Filled.Clear else Icons.Filled.Add, "Follow/Unfollow association" ) Spacer(modifier = Modifier.padding(followSpaceInside)) + // Text for follow/unfollow Text( if (isFollowed) stringResource(R.string.association_details_unfollow) else stringResource(R.string.association_details_follow), @@ -85,6 +94,7 @@ fun AssociationDetails( } } Spacer(modifier = Modifier.height(verticalSpace)) + // Pager for association description and events Pager( listOf( Pair(stringResource(R.string.association_details_description)) { @@ -98,20 +108,26 @@ fun AssociationDetails( } } +// This Composable function displays the description of an association. @Composable fun AssociationDescription( - association: Association, - verticalSpace: Dp, - onTagPressed: (Tag) -> Unit + association: Association, // The association to display + verticalSpace: Dp, // The vertical space to use in the layout + onTagPressed: (Tag) -> Unit // Function to handle tag press events ) { + // Convert the set of related tags to a list val tags = association.relatedTags.toList() val spaceBetweenTagChips = 6.dp + + // Start of the layout Column { + // Display the related tags in a horizontal scrollable row LazyRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(spaceBetweenTagChips) ) { items(tags) { tag -> + // Each tag is displayed as a chip AssistChip( onClick = { onTagPressed(tag) }, label = { Text(tag.name) }, @@ -128,7 +144,9 @@ fun AssociationDescription( ) } } + // Display the association description Text(association.description) + // If the association has a URL, display it as a hyperlink if (association.url != null) { Spacer(modifier = Modifier.height(verticalSpace)) Text( @@ -141,7 +159,9 @@ fun AssociationDescription( } } +// This Composable function displays the events of an association. @Composable fun AssociationEvents(events: List, isOnline: Boolean, refreshEvents: () -> Unit) { + // Use a ListDrawer to display the events ListDrawer(events, isOnline, refreshEvents) } diff --git a/app/src/main/java/com/github/swent/echo/compose/association/AssociationListScreen.kt b/app/src/main/java/com/github/swent/echo/compose/association/AssociationListScreen.kt index 55d80bfc3..5d569265b 100644 --- a/app/src/main/java/com/github/swent/echo/compose/association/AssociationListScreen.kt +++ b/app/src/main/java/com/github/swent/echo/compose/association/AssociationListScreen.kt @@ -12,43 +12,57 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.github.swent.echo.data.model.Association -/** A screen which displays a list of associations */ +/** + * A screen which displays a list of associations. This Composable function takes a list of + * associations and a function to handle row clicks. It uses a LazyColumn to efficiently display a + * potentially large list of associations. + */ @Composable fun AssociationListScreen( - associationList: List, - onRowClicked: (Association) -> Unit = {}, + associationList: List, // The list of associations to display + onRowClicked: (Association) -> Unit = {}, // Function to handle row clicks ) { + // LazyColumn is a vertically scrolling list that only composes and lays out the currently + // visible items LazyColumn(modifier = Modifier.testTag("association_list_screen")) { + // For each association in the list, create an AssociationListElement items(associationList.size) { index -> AssociationListElement( - association = associationList[index], - onRowClicked = onRowClicked + association = associationList[index], // The association to display + onRowClicked = onRowClicked // The function to call when the row is clicked ) } } } -/** An element in the association list */ +/** + * An element in the association list. This Composable function takes an association and a function + * to handle clicks. It uses a ListItem to display the association's name and handle click events. + */ @Composable fun AssociationListElement( - association: Association, - onRowClicked: (Association) -> Unit, + association: Association, // The association to display + onRowClicked: (Association) -> Unit, // The function to call when the row is clicked ) { - val boxInsidePadding = 5.dp - val tonalElevation = 5.dp + val boxInsidePadding = 5.dp // The padding inside the box + val tonalElevation = 5.dp // The elevation of the box + + // ListItem is a Material Design implementation of [list + // items](https://material.io/components/lists) ListItem( headlineContent = { + // Display the association's name Text( - text = association.name, - textAlign = TextAlign.Center, + text = association.name, // The association's name + textAlign = TextAlign.Center, // Center the text modifier = - Modifier.padding(boxInsidePadding) - .testTag("association_name_button_${association.name}") + Modifier.padding(boxInsidePadding) // Add padding + .testTag("association_name_button_${association.name}") // Add a test tag ) }, modifier = - Modifier.clickable { onRowClicked(association) } - .testTag("association_list_${association.name}"), - tonalElevation = tonalElevation, + Modifier.clickable { onRowClicked(association) } // Make the ListItem clickable + .testTag("association_list_${association.name}"), // Add a test tag + tonalElevation = tonalElevation, // Set the elevation ) } diff --git a/app/src/main/java/com/github/swent/echo/compose/association/AssociationScreen.kt b/app/src/main/java/com/github/swent/echo/compose/association/AssociationScreen.kt index 4844dd04a..97c3ed99d 100644 --- a/app/src/main/java/com/github/swent/echo/compose/association/AssociationScreen.kt +++ b/app/src/main/java/com/github/swent/echo/compose/association/AssociationScreen.kt @@ -23,11 +23,22 @@ import com.github.swent.echo.ui.navigation.NavigationActions import com.github.swent.echo.ui.navigation.Routes import com.github.swent.echo.viewmodels.association.AssociationViewModel +/** + * A screen which displays associations. This Composable function takes an AssociationViewModel and + * NavigationActions. It uses a Scaffold to provide a layout structure for the screen. + */ @Composable -fun AssociationScreen(associationViewModel: AssociationViewModel, navActions: NavigationActions) { +fun AssociationScreen( + associationViewModel: + AssociationViewModel, // The ViewModel that provides the data for the screen + navActions: NavigationActions // The actions that can be performed for navigation +) { + // Collect the state of followed associations, committee associations, and all associations val followedAssociations by associationViewModel.followedAssociations.collectAsState() val committeeAssociations by associationViewModel.committeeAssociations.collectAsState() val showAllAssociations by associationViewModel.showAllAssociations.collectAsState() + + // Create a list of pages for the Pager val pages = listOf( Pair("Followed Associations", followedAssociations), @@ -35,21 +46,29 @@ fun AssociationScreen(associationViewModel: AssociationViewModel, navActions: Na Pair("All Associations", showAllAssociations) ) + // Collect the state of the current association page and the initial page val currentAssociationPage by associationViewModel.currentAssociationPage.collectAsState() val initialPage by associationViewModel.initialPage.collectAsState() + // Collect the state of the searched text val searched by associationViewModel.searched.collectAsState() + // Collect the state of the online status val isOnline by associationViewModel.isOnline.collectAsState() + // Define the space between the search bar and the pages val spaceBetweenSearchAndPages = 8.dp + // Scaffold provides a framework for material design surfaces Scaffold( topBar = { + // Display the title and back button EventTitleAndBackButton(stringResource(R.string.hamburger_associations)) { if (currentAssociationPage == Association.EMPTY) { + // Navigate to the map if the current association page is empty navActions.navigateTo(Routes.MAP) } else { + // Set the current association page to empty associationViewModel.setCurrentAssociationPage(Association.EMPTY) } } @@ -60,13 +79,17 @@ fun AssociationScreen(associationViewModel: AssociationViewModel, navActions: Na modifier = Modifier.padding(paddingValues), horizontalAlignment = Alignment.CenterHorizontally ) { + // Check if the current association page is empty, which means we must display the + // default screen if (currentAssociationPage == Association.EMPTY) { + // Display the search bar SearchBar( stringResource(R.string.associations_categories), searched, associationViewModel::setSearched ) Spacer(modifier = Modifier.height(spaceBetweenSearchAndPages)) + // Display the pager with the list of pages Pager( pages.mapIndexed { id, page -> Pair(page.first) { @@ -80,6 +103,7 @@ fun AssociationScreen(associationViewModel: AssociationViewModel, navActions: Na initialPage ) } else { + // Display the association details AssociationDetails( currentAssociationPage, followedAssociations.contains(currentAssociationPage), diff --git a/app/src/main/java/com/github/swent/echo/compose/components/Dropdown.kt b/app/src/main/java/com/github/swent/echo/compose/components/Dropdown.kt index f6001c874..cf36f0cc7 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/Dropdown.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/Dropdown.kt @@ -23,36 +23,50 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp +/** + * A Composable function that creates a dropdown menu. This function takes a title, a list of items, + * a selected item index, and a callback function. + */ @Composable fun Dropdown( - title: String, - items: List, - selectedItem: Int, - callback: (Int) -> Unit, + title: String, // The title of the dropdown + items: List, // The list of items to be displayed in the dropdown + selectedItem: Int, // The index of the selected item + callback: (Int) -> Unit, // The callback function to be called when an item is selected ) { + // Define the dimensions and states for the dropdown val width = 170.dp val itemsHeight = 40.dp - var expanded by remember { mutableStateOf(false) } + var expanded by remember { + mutableStateOf(false) + } // The state of the dropdown (expanded or not) val verticalMaxSize = (LocalConfiguration.current.screenHeightDp / 4).dp val cornerRadius = 5.dp + + // Create a Box layout to contain the dropdown Box { + // Create a Button that expands or collapses the dropdown when clicked Button( onClick = { expanded = true }, modifier = Modifier.width(width).height(itemsHeight).testTag("dropdown_button"), shape = RoundedCornerShape(cornerRadius), ) { + // Display the selected item or the title if no item is selected Text(if (selectedItem < 0) title else items[selectedItem]) + // Display an arrow icon indicating the state of the dropdown Icon( if (!expanded) Icons.Filled.KeyboardArrowDown else Icons.Filled.KeyboardArrowUp, contentDescription = "arrow" ) } + // Create a DropdownMenu that displays the items DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.width(width).heightIn(max = verticalMaxSize).testTag("dropdown_menu") ) { + // Create a DropdownMenuItem for the null item DropdownMenuItem( onClick = { callback(-1) @@ -61,6 +75,7 @@ fun Dropdown( text = { Text("---") }, modifier = Modifier.height(itemsHeight).testTag("dropdown_item_null") ) + // Create a DropdownMenuItem for each item in the list items.forEachIndexed { id, item -> DropdownMenuItem( onClick = { diff --git a/app/src/main/java/com/github/swent/echo/compose/components/HamburgerMenuDrawerSheet.kt b/app/src/main/java/com/github/swent/echo/compose/components/HamburgerMenuDrawerSheet.kt index d267364a9..b6bee76bb 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/HamburgerMenuDrawerSheet.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/HamburgerMenuDrawerSheet.kt @@ -72,17 +72,6 @@ fun HamburgerMenuDrawerSheet( selectedIcon = Icons.Filled.DateRange, navOnClick = { navActions.navigateTo(Routes.MY_EVENTS) } ), - /* - NavigationItem( - title = stringResource(id = R.string.hamburger_friends), - selectedIcon = Icons.Filled.Face, - ), - NavigationItem( - title = stringResource(id = R.string.hamburger_settings), - selectedIcon = Icons.Filled.Settings, - ), - - */ NavigationItem( title = stringResource(id = R.string.hamburger_create_event), selectedIcon = Icons.Filled.AddCircle, @@ -91,12 +80,6 @@ fun HamburgerMenuDrawerSheet( navActions.navigateTo(Routes.CREATE_EVENT.build(encodedMapCenter)) } ), - /* - NavigationItem( - title = stringResource(id = R.string.hamburger_add_friends), - selectedIcon = Icons.Filled.Add, - ), - */ NavigationItem( title = stringResource(R.string.hamburger_associations), selectedIcon = Icons.Filled.Star, @@ -125,14 +108,12 @@ fun HamburgerMenuDrawerSheet( Column( modifier = Modifier.align(Alignment.TopStart).padding(8.dp).testTag("profile_sheet") ) { - // TO-DO: Replace with actual profile picture Image( modifier = Modifier.testTag("profile_picture"), painter = painterResource(id = R.drawable.ic_launcher_foreground), contentDescription = "profile picture" ) Row(modifier = Modifier.padding(8.dp).testTag("profile_info")) { - // TO-DO: Replace with actual name and class Text( text = profileName, modifier = Modifier.testTag("profile_name"), diff --git a/app/src/main/java/com/github/swent/echo/compose/components/HomeScreen.kt b/app/src/main/java/com/github/swent/echo/compose/components/HomeScreen.kt index b3ce225ae..07461a8f4 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/HomeScreen.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/HomeScreen.kt @@ -210,6 +210,7 @@ private fun Content( } } +// Colors for the epfl/section/class tags val colorEpfl = Color.Red.copy(0.6f) val colorSection = Color.Blue.copy(0.6f) val colorClass = Color.Green.copy(0.6f) diff --git a/app/src/main/java/com/github/swent/echo/compose/components/Hypertext.kt b/app/src/main/java/com/github/swent/echo/compose/components/Hypertext.kt index 3a23d1850..28af72420 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/Hypertext.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/Hypertext.kt @@ -10,9 +10,17 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.style.TextDecoration +/** + * A Composable function that creates a clickable hyperlink. This function takes a URL as a string. + */ @Composable fun Hypertext(url: String) { + // Get the current context val context = LocalContext.current + + // Create an AnnotatedString with the URL + // The URL is styled with blue color and underline + // A string annotation is added with the tag "URL" val annotatedString = AnnotatedString.Builder(url) .apply { @@ -25,6 +33,8 @@ fun Hypertext(url: String) { } .toAnnotatedString() + // Create a ClickableText with the AnnotatedString + // When clicked, it opens the URL in a browser ClickableText( text = annotatedString, onClick = { offset -> diff --git a/app/src/main/java/com/github/swent/echo/compose/components/JoinEventButton.kt b/app/src/main/java/com/github/swent/echo/compose/components/JoinEventButton.kt index b95b52327..ae1c152fd 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/JoinEventButton.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/JoinEventButton.kt @@ -26,19 +26,29 @@ import com.github.swent.echo.viewmodels.myevents.MyEventsViewModel */ @Composable fun JoinEventButton(event: Event, isOnline: Boolean, buttonWidth: Dp, refreshEvents: () -> Unit) { + // Get the ViewModel for managing the user's events. val myEventsViewModel: MyEventsViewModel = hiltViewModel() + // Observe the list of events that the user has joined. val joinedEvents by myEventsViewModel.joinedEvents.collectAsState() + // Create a button for joining or leaving the event. Button( + // The button is enabled if the user is online and the event is not full. enabled = isOnline && (event.participantCount < event.maxParticipants), + // When the button is clicked, the user joins or leaves the event. onClick = { myEventsViewModel.joinOrLeaveEvent(event, refreshEvents) }, + // Set the width of the button and a test tag for testing purposes. modifier = androidx.compose.ui.Modifier.width(buttonWidth) .testTag("list_join_event_${event.eventId}") ) { + // The text of the button depends on whether the user has joined the event. Text( if (joinedEvents.map { it.eventId }.contains(event.eventId)) - stringResource(id = R.string.list_drawer_leave_event) - else stringResource(id = R.string.list_drawer_join_event) + // If the user has joined the event, the button says "Leave". + stringResource(id = R.string.list_drawer_leave_event) + else + // If the user has not joined the event, the button says "Join". + stringResource(id = R.string.list_drawer_join_event) ) } } diff --git a/app/src/main/java/com/github/swent/echo/compose/components/ListDrawer.kt b/app/src/main/java/com/github/swent/echo/compose/components/ListDrawer.kt index 6c9d66760..62510542c 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/ListDrawer.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/ListDrawer.kt @@ -119,18 +119,25 @@ fun EventListItem( canModifyEvent: Boolean, distance: Double?, ) { + // Format the date to be displayed val date = event.startDate.format(DateTimeFormatter.ofPattern("E, dd.MM.yyyy HH:mm")) + // Format the association name to be displayed val association = event.organizer?.name?.let { "$it • " } ?: "" + // Format the distance to be displayed val dist = distance?.let { "${it}km • " } ?: "" + // Layout constants val spaceBetweenTagChips = 6.dp val spaceBetweenElements = 12.dp + // Colors val onSurfaceFaded = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) + // Display the event in a card Card( modifier = Modifier.clickable { + // If the event is already selected, deselect it, otherwise select it if (selectedEvent.value == event.eventId) selectedEvent.value = "" else selectedEvent.value = event.eventId } @@ -138,12 +145,14 @@ fun EventListItem( .fillMaxWidth() ) { Column(modifier = Modifier.padding(16.dp)) { + // Fist floor of the card, with the title, date, association and number of participants Row( modifier = Modifier.fillMaxWidth().testTag("list_event_row_${event.eventId}"), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Column { + // Title Text( text = event.title, modifier = @@ -154,12 +163,14 @@ fun EventListItem( style = MaterialTheme.typography.titleLarge, ) Spacer(modifier = Modifier.height(4.dp)) + // Date, association and number of participants Text( text = "$association$dist$date", style = MaterialTheme.typography.bodySmall, modifier = Modifier.testTag("list_event_date_${event.eventId}"), ) } + // Number of participants Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, @@ -176,8 +187,10 @@ fun EventListItem( ) } } - + // Second floor of the card, with the tags, description and buttons. Appears only if + // the event is selected if (selectedEvent.value == event.eventId) { + // Tags corresponding to the event val tags = event.tags.toList() val iconButtonColors = IconButtonDefaults.iconButtonColors( @@ -188,12 +201,14 @@ fun EventListItem( Spacer(modifier = Modifier.height(spaceBetweenElements)) HorizontalDivider(thickness = 2.dp, color = onSurfaceFaded) Spacer(modifier = Modifier.height(spaceBetweenElements)) + // Display the related tags in a horizontal scrollable row LazyRow( modifier = Modifier.fillMaxWidth().testTag("list_event_details_${event.eventId}"), horizontalArrangement = Arrangement.spacedBy(spaceBetweenTagChips) ) { items(tags) { tag -> + // Each tag is displayed as a chip AssistChip( onClick = { onTagPressed(tag) }, label = { Text(tag.name) }, @@ -211,16 +226,19 @@ fun EventListItem( } } Spacer(modifier = Modifier.height(spaceBetweenElements)) + // Description of the event Text( event.description, modifier = Modifier.testTag("list_event_description_${event.eventId}") ) Spacer(modifier = Modifier.height(spaceBetweenElements)) + // Buttons to view the event on the map and to modify the event Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { Row { + // View on map button if (viewOnMap != null) { IconButton( onClick = { viewOnMap(event) }, @@ -234,6 +252,7 @@ fun EventListItem( } Spacer(modifier = Modifier.width(4.dp)) } + // Modify button if (canModifyEvent) { IconButton( onClick = { modify(event) }, @@ -247,6 +266,7 @@ fun EventListItem( } } } + // Join event button JoinEventButton(event, isOnline, 130.dp, refreshEvents) } } diff --git a/app/src/main/java/com/github/swent/echo/compose/components/Pager.kt b/app/src/main/java/com/github/swent/echo/compose/components/Pager.kt index 4e263f203..11ba11091 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/Pager.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/Pager.kt @@ -36,24 +36,31 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable fun Pager(content: List Unit>>, initialPage: Int = 0) { + // Remember the state of the pager, including the current page and the total number of pages. val pagerState = rememberPagerState(initialPage = initialPage, pageCount = { content.size }) + // Remember a CoroutineScope for launching coroutines. val coroutineScope = rememberCoroutineScope() + // Define some constants for padding and spacing. val itemsPadding = 2.dp val itemsWeight = 1f val spaceBetweenTitleAndItem = 8.dp val titlePadding = 8.dp val underlineHeight = 1.dp + // Create a column for the pager. Column(modifier = Modifier.testTag("pager")) { Box { Row( Modifier.wrapContentHeight().fillMaxWidth().align(Alignment.TopCenter), horizontalArrangement = Arrangement.Center ) { + // For each page, create a title and an underline. content.forEachIndexed { id, item -> Column( modifier = Modifier.padding(horizontal = itemsPadding).weight(itemsWeight), horizontalAlignment = Alignment.CenterHorizontally ) { + // The title is a button that scrolls to the corresponding page when + // clicked. TextButton( onClick = { coroutineScope.launch { @@ -70,6 +77,7 @@ fun Pager(content: List Unit>>, initialPage: Int textAlign = TextAlign.Center ) } + // The underline is visible only for the current page. if (pagerState.currentPage == id) { Box( modifier = @@ -83,12 +91,15 @@ fun Pager(content: List Unit>>, initialPage: Int } } } + // Add some space between the titles and the content. Spacer(modifier = Modifier.height(spaceBetweenTitleAndItem)) + // Create a horizontal pager for the content. HorizontalPager( state = pagerState, modifier = Modifier.fillMaxHeight(), verticalAlignment = Alignment.Top, ) { + // Display the content of the current page. content[it].second() } } diff --git a/app/src/main/java/com/github/swent/echo/compose/components/SearchBar.kt b/app/src/main/java/com/github/swent/echo/compose/components/SearchBar.kt index c4d0638a8..8fa601d92 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/SearchBar.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/SearchBar.kt @@ -15,28 +15,40 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp +// This Composable function creates a search bar. @Composable fun SearchBar(title: String, searched: String, onSearchChanged: (String) -> Unit) { + // Define some constants for padding and corner radius. val paddingHorizontal = 10.dp val cornerRadius = 10.dp + // Create an outlined text field for the search bar. OutlinedTextField( + // The label of the text field is the title of the search bar. label = { Text(title) }, + // The value of the text field is the current search query. value = searched, + // When the value changes, call the onSearchChanged function. onValueChange = onSearchChanged, + // The trailing icon depends on whether the search query is empty. trailingIcon = { if (searched.isBlank()) { + // If the search query is empty, show a search icon. Icon(Icons.Outlined.Search, "search") } else { + // If the search query is not empty, show a close icon that clears the search query + // when clicked. IconButton( onClick = { onSearchChanged("") }, content = { Icon(Icons.Outlined.Close, "close") } ) } }, + // Set the width and padding of the text field and a test tag for testing purposes. modifier = Modifier.fillMaxWidth() .padding(horizontal = paddingHorizontal) .testTag("search_bar_$title"), + // Set the shape of the text field to be a rounded rectangle. shape = RoundedCornerShape(cornerRadius) ) } diff --git a/app/src/main/java/com/github/swent/echo/compose/components/SearchMenuSheet.kt b/app/src/main/java/com/github/swent/echo/compose/components/SearchMenuSheet.kt index 8d60c65b0..7566b9c56 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/SearchMenuSheet.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/SearchMenuSheet.kt @@ -27,6 +27,7 @@ import com.github.swent.echo.compose.components.searchmenu.SearchMenuFilters import com.github.swent.echo.viewmodels.MapOrListMode import com.github.swent.echo.viewmodels.tag.TagViewModel +// This Composable function creates a search menu sheet with various filters and options. @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchMenuSheet( @@ -46,12 +47,13 @@ fun SearchMenuSheet( initialPage: Int, mode: MapOrListMode ) { - // TagViewModel + // Get the TagViewModel from Hilt. val tagViewModel: TagViewModel = hiltViewModel { factory -> factory.create() } + // Remember the state of the modal bottom sheet. val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) { value -> if (value == SheetValue.Expanded) { @@ -59,10 +61,14 @@ fun SearchMenuSheet( } true } + + // Define some constants for dimensions and padding. val bottomSheetHeight = 500.dp val paddingValues = 5.dp val pageHeight = 385.dp val paddingBottomResetFilters = 10.dp + + // Create a modal bottom sheet. ModalBottomSheet( modifier = Modifier.fillMaxWidth().height(bottomSheetHeight).testTag("search_menu_sheet"), onDismissRequest = onDismiss, @@ -75,13 +81,14 @@ fun SearchMenuSheet( Column( modifier = Modifier.fillMaxWidth().height(pageHeight).align(Alignment.TopCenter) ) { - // Sheet content + // Create a search bar at the top of the sheet. SearchBar( stringResource(R.string.search_menu_sheet_search_interests), filters.searchEntry, searchEntryCallback ) - // Display filters or discover according to the selected mode + + // Create a pager with two pages: one for filters and one for discover. Pager( listOf( Pair(stringResource(R.string.search_menu_sheet_filters)) { @@ -105,7 +112,8 @@ fun SearchMenuSheet( initialPage ) } - // Close Search Button + + // Create a button at the bottom of the sheet that resets the filters. Row( modifier = Modifier.fillMaxWidth() diff --git a/app/src/main/java/com/github/swent/echo/compose/components/TopAppBar.kt b/app/src/main/java/com/github/swent/echo/compose/components/TopAppBar.kt index 179b5cadb..df393497d 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/TopAppBar.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/TopAppBar.kt @@ -45,11 +45,14 @@ fun TopAppBar( resetSearch: () -> Unit, switchMode: () -> Unit ) { - // Scroll behavior for the top app bar, makes it pinned + // Define the scroll behavior for the top app bar. In this case, it's pinned, meaning it stays + // at the top of the screen. val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + // Create a top app bar that is centered. CenterAlignedTopAppBar( title = { + // The title of the app bar is the app's title. Text( stringResource(R.string.app_title), maxLines = 1, @@ -58,6 +61,7 @@ fun TopAppBar( ) }, navigationIcon = { // hamburger menu + // The navigation icon is a hamburger menu that opens the drawer when clicked. IconButton( onClick = { scope.launch { drawerState.open() } }, modifier = Modifier.testTag("menu_button") @@ -69,6 +73,8 @@ fun TopAppBar( } }, actions = { // search reset button and list/map mode switch button + // If the search mode is active, show a search reset button that resets the search when + // clicked. if (searchMode) { IconButton( onClick = resetSearch, @@ -80,6 +86,7 @@ fun TopAppBar( ) } } + // Show a list/map mode switch button that switches the mode when clicked. IconButton(onClick = switchMode, modifier = Modifier.testTag("list_map_mode_button")) { Icon( painter = diff --git a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/FiltersContainer.kt b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/FiltersContainer.kt index f26e661ff..3ceda5f8d 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/FiltersContainer.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/FiltersContainer.kt @@ -2,15 +2,16 @@ package com.github.swent.echo.compose.components.searchmenu import com.github.swent.echo.viewmodels.SortBy +// This data class is responsible for holding the state of various filters. data class FiltersContainer( - var searchEntry: String, - var epflChecked: Boolean, - var sectionChecked: Boolean, - var classChecked: Boolean, - var pendingChecked: Boolean, - var confirmedChecked: Boolean, - var fullChecked: Boolean, - var from: Float, - var to: Float, - var sortBy: SortBy + var searchEntry: String, // The search query entered by the user + var epflChecked: Boolean, // Whether the EPFL filter is checked + var sectionChecked: Boolean, // Whether the Section filter is checked + var classChecked: Boolean, // Whether the Class filter is checked + var pendingChecked: Boolean, // Whether the Pending filter is checked + var confirmedChecked: Boolean, // Whether the Confirmed filter is checked + var fullChecked: Boolean, // Whether the Full filter is checked + var from: Float, // The lower bound of the range filter + var to: Float, // The upper bound of the range filter + var sortBy: SortBy // The selected sorting option ) diff --git a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuDiscover.kt b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuDiscover.kt index f1bed5b65..5e3bb86d7 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuDiscover.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuDiscover.kt @@ -115,19 +115,22 @@ fun SearchMenuDiscover(searchEntryCallback: (String) -> Unit, tagViewModel: TagV // Component that displays a tag in the discover mode @Composable fun SearchMenuDiscoverItem( - tag: Tag, - onTagClicked: (Tag) -> Unit, - selectedTag: String, - subTags: List, - randomSeed: Int + tag: Tag, // The tag associated with this item + onTagClicked: (Tag) -> Unit, // Callback function when the tag is clicked + selectedTag: String, // The currently selected tag + subTags: List, // List of subtags associated with this item + randomSeed: Int // Seed for randomizing the display of subtags ) { + // Create a box that fills the width, has a specific height, padding, rounded corners, and a + // background color + // that changes based on whether the tag is selected or not. It also has a click listener that + // triggers the callback function. Box( modifier = Modifier.fillMaxWidth() .height(90.dp) .padding(3.dp) .clip(RoundedCornerShape(8.dp)) - // Change the background color of the tag if it is selected .background( if (selectedTag == tag.name) { MaterialTheme.colorScheme.primaryContainer @@ -138,17 +141,21 @@ fun SearchMenuDiscoverItem( .clickable(onClick = { onTagClicked(tag) }) .testTag("discover_child_${tag.name}") ) { + // Create a column that aligns its children in the center. Column( modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally ) { + // Determine the text color based on whether the tag is selected or not. val textColor = if (selectedTag == tag.name) { MaterialTheme.colorScheme.onPrimaryContainer } else { MaterialTheme.colorScheme.onSecondaryContainer } + // Display the tag name. Text(text = tag.name, color = textColor) + // If there are subtags, display them in a randomized order. if (subTags.isNotEmpty()) { Text( modifier = Modifier.padding(5.dp), diff --git a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFilters.kt b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFilters.kt index dfd09db33..4edede320 100644 --- a/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFilters.kt +++ b/app/src/main/java/com/github/swent/echo/compose/components/searchmenu/SearchMenuFilters.kt @@ -40,21 +40,21 @@ import java.time.format.DateTimeFormatter import kotlin.math.roundToInt import kotlin.math.roundToLong -/** Composable to display the filters sheet */ +// This Composable function is responsible for displaying the search menu filters. @Composable fun SearchMenuFilters( - filters: FiltersContainer, - epflCallback: () -> Unit, - sectionCallback: () -> Unit, - classCallback: () -> Unit, - pendingCallback: () -> Unit, - confirmedCallback: () -> Unit, - fullCallback: () -> Unit, - sortByCallback: (Int) -> Unit, - timeFilterCallback: (Float, Float) -> Unit, - mode: MapOrListMode + filters: FiltersContainer, // The current state of the filters + epflCallback: () -> Unit, // Callback function when the EPFL filter is toggled + sectionCallback: () -> Unit, // Callback function when the section filter is toggled + classCallback: () -> Unit, // Callback function when the class filter is toggled + pendingCallback: () -> Unit, // Callback function when the pending filter is toggled + confirmedCallback: () -> Unit, // Callback function when the confirmed filter is toggled + fullCallback: () -> Unit, // Callback function when the full filter is toggled + sortByCallback: (Int) -> Unit, // Callback function when the sort by filter is changed + timeFilterCallback: (Float, Float) -> Unit, // Callback function when the time filter is changed + mode: MapOrListMode // The current display mode (map or list) ) { - // Content of the Events for filters + // Define the items for the "Events for" filter section val eventsForItems = listOf( CheckBoxItems( @@ -83,7 +83,7 @@ fun SearchMenuFilters( } ) - // Content of the Events Status filters + // Define the items for the "Events status" filter section val eventsStatusItems = listOf( CheckBoxItems( @@ -111,15 +111,21 @@ fun SearchMenuFilters( fullCallback() } ) + + // Define the list of followed associations and the currently selected association val followedAssociations = listOf("1", "2", "3", "4", "5") var selectedAssociation = -1 val associationCallback = { id: Int -> selectedAssociation = id } + + // Define the vertical space between elements val verticalSpacer = 8.dp + // Define the layout of the filters Column( modifier = Modifier.fillMaxSize().testTag("search_menu_filters_content"), horizontalAlignment = Alignment.CenterHorizontally ) { + // Display the dropdowns for selecting the association and the sort order TwoBoxesDisplayer( { Dropdown( @@ -129,6 +135,7 @@ fun SearchMenuFilters( associationCallback ) }, + // Display the sort by dropdown only in list mode if (mode == MapOrListMode.LIST) { { Dropdown( @@ -143,6 +150,7 @@ fun SearchMenuFilters( } ) Spacer(modifier = Modifier.height(verticalSpacer)) + // Display the checkboxes for selecting the event filters TwoBoxesDisplayer( { CheckBoxesDisplayer( @@ -158,6 +166,7 @@ fun SearchMenuFilters( } ) Spacer(modifier = Modifier.height(verticalSpacer)) + // Display the date filter DateFilter(filters, timeFilterCallback) } } @@ -171,31 +180,44 @@ data class CheckBoxItems( val callback: () -> Unit ) -/** Composable to display the checkboxes in the correct format */ @Composable fun CheckBoxesDisplayer(title: String, checkBoxItems: List) { + // Define the space between the title and the items, and between the items themselves val spaceBetweenTitleAndItems = 10.dp val spaceBetweenItems = 5.dp + // Define the size of the checkboxes val checkboxSize = 25.dp + + // Start a column to arrange the elements vertically Column { + // Display the title Text(title, modifier = Modifier.testTag("checkboxes_title")) + // Add some space after the title Spacer(modifier = Modifier.height(spaceBetweenTitleAndItems)) + + // For each item in the list of checkboxes checkBoxItems.forEach { checkBoxItem -> + // Start a row to arrange the elements horizontally Row(modifier = Modifier.testTag("${checkBoxItem.contentDescription}_checkbox_row")) { + // Display the icon for the checkbox Icon( checkBoxItem.icon, contentDescription = checkBoxItem.contentDescription, tint = checkBoxItem.tint ) + // Display the checkbox itself Checkbox( checked = checkBoxItem.checked, + // When the checkbox is clicked, call the callback function onCheckedChange = { checkBoxItem.callback() }, modifier = Modifier.size(checkboxSize) .testTag("${checkBoxItem.contentDescription}_checkbox") ) + // Display the description of the checkbox Text(checkBoxItem.contentDescription) } + // Add some space after the checkbox Spacer(modifier = Modifier.height(spaceBetweenItems)) } } @@ -283,11 +305,21 @@ fun DateFilter(filters: FiltersContainer, timeSliderCallback: (Float, Float) -> @Composable fun TwoBoxesDisplayer(firstBox: @Composable () -> Unit, secondBox: (@Composable () -> Unit)?) { - val padding = 20.dp + // Define the padding around the boxes + val padding = 12.dp + + // Start a box that fills the maximum width of the parent Box(modifier = Modifier.fillMaxWidth()) { - Box(modifier = Modifier.align(Alignment.TopStart).padding(start = padding)) { firstBox() } + // Start a box for the first composable, align it to the start (left) and add padding + Box(modifier = Modifier.align(Alignment.TopStart).padding(start = padding)) { + firstBox() // Call the first composable + } + // If a second composable is provided if (secondBox != null) { - Box(modifier = Modifier.align(Alignment.TopEnd).padding(end = padding)) { secondBox() } + // Start a box for the second composable, align it to the end (right) and add padding + Box(modifier = Modifier.align(Alignment.TopEnd).padding(end = padding)) { + secondBox() // Call the second composable + } } } } diff --git a/app/src/main/java/com/github/swent/echo/compose/myevents/MyEventsScreen.kt b/app/src/main/java/com/github/swent/echo/compose/myevents/MyEventsScreen.kt index 11d290811..7ee3705f5 100644 --- a/app/src/main/java/com/github/swent/echo/compose/myevents/MyEventsScreen.kt +++ b/app/src/main/java/com/github/swent/echo/compose/myevents/MyEventsScreen.kt @@ -18,26 +18,42 @@ import com.github.swent.echo.ui.navigation.NavigationActions import com.github.swent.echo.ui.navigation.Routes import com.github.swent.echo.viewmodels.myevents.MyEventsViewModel +// This Composable function creates a screen for displaying the user's events. @Composable -fun MyEventsScreen(myEventsViewModel: MyEventsViewModel, navActions: NavigationActions) { +fun MyEventsScreen( + myEventsViewModel: MyEventsViewModel, // ViewModel for managing the user's events + navActions: NavigationActions // Actions for navigating between screens +) { + // Create a scaffold, which is a Material Design layout structure. Scaffold( + // The top bar of the scaffold is a title and a back button. topBar = { EventTitleAndBackButton(stringResource(R.string.hamburger_my_events)) { + // When the back button is clicked, navigate to the map screen. navActions.navigateTo(Routes.MAP) } }, - modifier = Modifier.fillMaxSize().testTag("my_events_screen") + modifier = Modifier.fillMaxSize().testTag("my_events_screen") // Fill the entire screen. ) { + // Get the list of events the user has joined. val joinedEventsList by myEventsViewModel.joinedEvents.collectAsState() + // Get the list of events the user has created. val createdEventsList by myEventsViewModel.createdEvents.collectAsState() + // Get whether the user is online. val isOnline by myEventsViewModel.isOnline.collectAsState() + // Create a box with padding. Box(modifier = Modifier.padding(it)) { + // Create a pager with two tabs: one for joined events and one for created events. Pager( listOf( + // The first tab is for joined events. Pair(stringResource(R.string.my_events_joined_events)) { + // Display the joined events in a list. ListDrawer(joinedEventsList, isOnline, myEventsViewModel::refreshEvents) }, + // The second tab is for created events. Pair(stringResource(R.string.my_events_created_events)) { + // Display the created events in a list. ListDrawer(createdEventsList, isOnline, myEventsViewModel::refreshEvents) } ) diff --git a/app/src/main/java/com/github/swent/echo/viewmodels/association/AssociationViewModel.kt b/app/src/main/java/com/github/swent/echo/viewmodels/association/AssociationViewModel.kt index 839632f06..7d9c4350a 100644 --- a/app/src/main/java/com/github/swent/echo/viewmodels/association/AssociationViewModel.kt +++ b/app/src/main/java/com/github/swent/echo/viewmodels/association/AssociationViewModel.kt @@ -14,31 +14,41 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -// represent associations +// This ViewModel is responsible for managing the data related to associations. @HiltViewModel class AssociationViewModel @Inject constructor( - private val repository: Repository, - private val authenticationService: AuthenticationService, - private val networkService: NetworkService + private val repository: Repository, // Repository for fetching data + private val authenticationService: AuthenticationService, // Service for managing authentication + private val networkService: NetworkService // Service for managing network ) : ViewModel() { + // All associations private lateinit var allAssociations: List + // All events private lateinit var allEvents: List + // Followed associations private val _followedAssociations = MutableStateFlow>(listOf()) val followedAssociations = _followedAssociations.asStateFlow() + // Committee associations private val _committeeAssociations = MutableStateFlow>(listOf()) val committeeAssociations = _committeeAssociations.asStateFlow() + // All associations to show private val _showAllAssociations = MutableStateFlow>(listOf()) val showAllAssociations = _showAllAssociations.asStateFlow() + // Initial page private val _initialPage = MutableStateFlow(0) val initialPage = _initialPage.asStateFlow() + // Current association page private val _currentAssociationPage = MutableStateFlow(Association.EMPTY) val currentAssociationPage = _currentAssociationPage.asStateFlow() + // Searched term private val _searched = MutableStateFlow("") val searched = _searched.asStateFlow() + // Online status val isOnline = networkService.isOnline + // Initialize the ViewModel init { viewModelScope.launch { val user = authenticationService.getCurrentUserID() ?: "" @@ -56,6 +66,7 @@ constructor( } } + // Handle follow/unfollow association fun onFollowAssociationChanged(association: Association) { if (_followedAssociations.value.contains(association)) { _followedAssociations.value -= association @@ -73,14 +84,17 @@ constructor( } } + // Set searched term fun setSearched(searched: String) { _searched.value = searched } + // Get events of an association fun associationEvents(association: Association): List { return allEvents.filter { it.organizer?.associationId == association.associationId } } + // Filter associations based on searched term fun filterAssociations(associations: List): List { return if (_searched.value.isEmpty()) { associations @@ -94,11 +108,13 @@ constructor( } } + // Set current association page fun setCurrentAssociationPage(association: Association, initialPage: Int = _initialPage.value) { _initialPage.value = initialPage _currentAssociationPage.value = association } + // Refresh events fun refreshEvents() { viewModelScope.launch { allEvents = repository.getAllEvents() } } diff --git a/app/src/main/java/com/github/swent/echo/viewmodels/myevents/MyEventsViewModel.kt b/app/src/main/java/com/github/swent/echo/viewmodels/myevents/MyEventsViewModel.kt index eafd09544..3c0f80700 100644 --- a/app/src/main/java/com/github/swent/echo/viewmodels/myevents/MyEventsViewModel.kt +++ b/app/src/main/java/com/github/swent/echo/viewmodels/myevents/MyEventsViewModel.kt @@ -12,21 +12,27 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +// This ViewModel is responsible for managing the data related to the user's events. @HiltViewModel class MyEventsViewModel @Inject constructor( - private val repository: Repository, - private val authenticationService: AuthenticationService, - private val networkService: NetworkService + private val repository: Repository, // Repository for fetching data + private val authenticationService: AuthenticationService, // Service for managing authentication + private val networkService: NetworkService // Service for managing network ) : ViewModel() { + // User ID private lateinit var user: String + // Joined events private val _joinedEvents = MutableStateFlow>(listOf()) val joinedEvents = _joinedEvents.asStateFlow() + // Created events private val _createdEvents = MutableStateFlow>(listOf()) val createdEvents = _createdEvents.asStateFlow() + // Online status val isOnline = networkService.isOnline + // Initialize the ViewModel init { viewModelScope.launch { user = authenticationService.getCurrentUserID() ?: "" @@ -35,6 +41,7 @@ constructor( } } + // Handle join/leave event fun joinOrLeaveEvent(event: Event, onFinished: () -> Unit) { viewModelScope.launch { if (_joinedEvents.value.map { it.eventId }.contains(event.eventId)) { @@ -47,6 +54,7 @@ constructor( } } + // Refresh events fun refreshEvents() { viewModelScope.launch { _joinedEvents.value = repository.getJoinedEvents(user)