From ff12e11ae85dd9def982e092f8004cc36dde13e7 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Sat, 14 Sep 2024 19:23:55 +0530 Subject: [PATCH] androidApp: SessionScreen: Initial support for viewing session information Signed-off-by: Aayush Gupta --- .../ccip/android/ui/components/TopAppBar.kt | 2 +- .../ccip/android/ui/navigation/NavGraph.kt | 5 + .../ccip/android/ui/navigation/Screen.kt | 8 +- .../ui/screens/schedule/ScheduleScreen.kt | 11 +- .../ui/screens/session/SessionScreen.kt | 139 ++++++++++++++++++ .../ui/screens/session/SessionViewModel.kt | 48 ++++++ androidApp/src/main/res/drawable/ic_info.xml | 14 ++ .../src/main/res/drawable/ic_location.xml | 14 ++ .../src/main/res/drawable/ic_podium.xml | 14 ++ .../src/main/res/drawable/ic_speaker.xml | 14 ++ androidApp/src/main/res/drawable/ic_time.xml | 14 ++ androidApp/src/main/res/values/strings.xml | 5 + 12 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionScreen.kt create mode 100644 androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionViewModel.kt create mode 100644 androidApp/src/main/res/drawable/ic_info.xml create mode 100644 androidApp/src/main/res/drawable/ic_location.xml create mode 100644 androidApp/src/main/res/drawable/ic_podium.xml create mode 100644 androidApp/src/main/res/drawable/ic_speaker.xml create mode 100644 androidApp/src/main/res/drawable/ic_time.xml diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/components/TopAppBar.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/components/TopAppBar.kt index c083b3a..fe2cfb6 100644 --- a/androidApp/src/main/java/app/opass/ccip/android/ui/components/TopAppBar.kt +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/components/TopAppBar.kt @@ -21,7 +21,7 @@ import androidx.navigation.NavHostController @Composable @OptIn(ExperimentalMaterial3Api::class) fun TopAppBar( - title: String, + title: String = String(), navHostController: NavHostController? = null, navigationIcon: @Composable () -> Unit = { DefaultNavigationIcon(navHostController) }, actions: @Composable() (RowScope.() -> Unit) = {} diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/NavGraph.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/NavGraph.kt index ef7de01..0f35927 100644 --- a/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/NavGraph.kt +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/NavGraph.kt @@ -20,6 +20,7 @@ import androidx.navigation.toRoute import app.opass.ccip.android.ui.screens.event.EventScreen import app.opass.ccip.android.ui.screens.eventpreview.EventPreviewScreen import app.opass.ccip.android.ui.screens.schedule.ScheduleScreen +import app.opass.ccip.android.ui.screens.session.SessionScreen @Composable fun SetupNavGraph(navHostController: NavHostController, startDestination: Screen) { @@ -46,6 +47,10 @@ fun SetupNavGraph(navHostController: NavHostController, startDestination: Screen backStackEntry.sharedViewModel(navHostController) ) } + + composable { backStackEntry -> + backStackEntry.toRoute().SessionScreen(navHostController) + } } } diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/Screen.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/Screen.kt index 2ddc944..04787c1 100644 --- a/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/Screen.kt +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/navigation/Screen.kt @@ -26,8 +26,14 @@ sealed class Screen(@StringRes val title: Int, @DrawableRes val icon: Int) { ) @Serializable - data class Schedule(val id: String) : Screen( + data class Schedule(val eventId: String) : Screen( title = R.string.schedule, icon = R.drawable.ic_schedule ) + + @Serializable + data class Session(val eventId: String, val sessionId: String) : Screen( + title = R.string.session, + icon = R.drawable.ic_podium + ) } diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/screens/schedule/ScheduleScreen.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/schedule/ScheduleScreen.kt index de63ba0..35d324c 100644 --- a/androidApp/src/main/java/app/opass/ccip/android/ui/screens/schedule/ScheduleScreen.kt +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/schedule/ScheduleScreen.kt @@ -56,7 +56,7 @@ fun Screen.Schedule.ScheduleScreen( val context = LocalContext.current val schedule by viewModel.schedule.collectAsStateWithLifecycle() - LaunchedEffect(key1 = Unit) { viewModel.getSchedule(this@ScheduleScreen.id) } + LaunchedEffect(key1 = Unit) { viewModel.getSchedule(this@ScheduleScreen.eventId) } @Composable fun LoadSessionPreviewItems( @@ -105,7 +105,11 @@ fun Screen.Schedule.ScheduleScreen( DateUtils.FORMAT_SHOW_TIME ), room = session.room - ) + ) { + navHostController.navigate( + Screen.Session(this@ScheduleScreen.eventId, session.id) + ) + } } } } @@ -151,11 +155,10 @@ fun SessionPreviewItem( Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { Text( text = room.ifBlank { stringResource(R.string.na) }, - color = MaterialTheme.colorScheme.onPrimary, fontSize = 12.sp, modifier = Modifier .clip(RoundedCornerShape(8.dp)) - .background(color = MaterialTheme.colorScheme.primary) + .background(color = MaterialTheme.colorScheme.secondaryContainer) .padding(horizontal = 10.dp) .shimmer(isLoading), ) diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionScreen.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionScreen.kt new file mode 100644 index 0000000..56ff912 --- /dev/null +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionScreen.kt @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: 2024 OPass + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.opass.ccip.android.ui.screens.session + +import android.text.format.DateUtils +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import app.opass.ccip.android.R +import app.opass.ccip.android.ui.components.TopAppBar +import app.opass.ccip.android.ui.navigation.Screen +import app.opass.ccip.network.models.schedule.Session + +@Composable +fun Screen.Session.SessionScreen( + navHostController: NavHostController, + viewModel: SessionViewModel = hiltViewModel() +) { + + val context = LocalContext.current + val session by viewModel.session.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = Unit) { + viewModel.getSession(this@SessionScreen.eventId, this@SessionScreen.sessionId) + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { TopAppBar(navHostController = navHostController) } + ) { paddingValues -> + if (session != null) { + LoadSession( + session = session!!, + dateTime = DateUtils.formatDateRange( + context, + viewModel.sdf.parse(session!!.start)!!.time, + viewModel.sdf.parse(session!!.end)!!.time, + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL + ), + modifier = Modifier.padding(paddingValues) + ) + } + } +} + +@Composable +fun LoadSession(session: Session, dateTime: String, modifier: Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + // Tags + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + session.tags?.fastForEach { tag -> + FilterChip(selected = true, onClick = {}, label = { Text(text = tag) }) + } + } + + // Title + Text(text = session.title, style = MaterialTheme.typography.headlineMedium) + + // Type, Room, and Time + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + session.type?.let { type -> + SessionInfoItem( + text = type.replaceFirstChar { it.titlecase() }, + drawableRes = R.drawable.ic_info + ) + } + SessionInfoItem( + text = session.room.ifBlank { stringResource(R.string.na) }, + drawableRes = R.drawable.ic_location + ) + SessionInfoItem(text = dateTime, drawableRes = R.drawable.ic_time) + SessionInfoItem( + text = session.speakers.joinToString(separator = ", "), + drawableRes = R.drawable.ic_podium + ) + } + } + } +} + +@Composable +private fun SessionInfoItem(text: String, @DrawableRes drawableRes: Int) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp) + ) { + Icon( + painter = painterResource(drawableRes), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text(text = text, style = MaterialTheme.typography.bodyLarge) + } +} diff --git a/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionViewModel.kt b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionViewModel.kt new file mode 100644 index 0000000..6849371 --- /dev/null +++ b/androidApp/src/main/java/app/opass/ccip/android/ui/screens/session/SessionViewModel.kt @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 OPass + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.opass.ccip.android.ui.screens.session + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.opass.ccip.helpers.PortalHelper +import app.opass.ccip.network.models.schedule.Session +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import javax.inject.Inject + +@HiltViewModel +class SessionViewModel @Inject constructor( + val sdf: SimpleDateFormat, + private val portalHelper: PortalHelper +): ViewModel() { + + private val _session: MutableStateFlow = MutableStateFlow(null) + val session = _session.asStateFlow() + + fun getSession(eventId: String, sessionId: String) { + viewModelScope.launch { + val session = portalHelper.getSession(eventId, sessionId) ?: return@launch + + // Resolve tags, speakers and session type to their original values + val queriedTags = session.tags?.map { portalHelper.getTag(eventId, it)!!.name } + val queriedSpeakers = session.speakers.map { portalHelper.getSpeaker(eventId, it)!!.name } + val queriedSessionType = if (session.type != null) { + portalHelper.getSessionType(eventId, session.type!!)!!.name + } else { + null + } + + _session.value = session.copy( + type = queriedSessionType, + tags = queriedTags, + speakers = queriedSpeakers + ) + } + } +} diff --git a/androidApp/src/main/res/drawable/ic_info.xml b/androidApp/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..b07dc74 --- /dev/null +++ b/androidApp/src/main/res/drawable/ic_info.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/androidApp/src/main/res/drawable/ic_location.xml b/androidApp/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..02f4371 --- /dev/null +++ b/androidApp/src/main/res/drawable/ic_location.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/androidApp/src/main/res/drawable/ic_podium.xml b/androidApp/src/main/res/drawable/ic_podium.xml new file mode 100644 index 0000000..698dc8d --- /dev/null +++ b/androidApp/src/main/res/drawable/ic_podium.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/androidApp/src/main/res/drawable/ic_speaker.xml b/androidApp/src/main/res/drawable/ic_speaker.xml new file mode 100644 index 0000000..99a93f9 --- /dev/null +++ b/androidApp/src/main/res/drawable/ic_speaker.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/androidApp/src/main/res/drawable/ic_time.xml b/androidApp/src/main/res/drawable/ic_time.xml new file mode 100644 index 0000000..e2ea367 --- /dev/null +++ b/androidApp/src/main/res/drawable/ic_time.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml index a3c5adb..fea2c35 100644 --- a/androidApp/src/main/res/values/strings.xml +++ b/androidApp/src/main/res/values/strings.xml @@ -26,4 +26,9 @@ Schedule N/A + + + Session + Speakers + Session introduction