From 7f200a14b915ded7433d785bed7e42c3bb11ba81 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 22 Jun 2024 19:31:50 +0200 Subject: [PATCH 01/80] added OnboardingScreenPreview Signed-off-by: Basler182 --- .../onboarding/onboarding/OnboardingScreen.kt | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/modules/onboarding/src/main/kotlin/edu/stanford/spezi/module/onboarding/onboarding/OnboardingScreen.kt b/modules/onboarding/src/main/kotlin/edu/stanford/spezi/module/onboarding/onboarding/OnboardingScreen.kt index ef54eaa02..ff5736e03 100644 --- a/modules/onboarding/src/main/kotlin/edu/stanford/spezi/module/onboarding/onboarding/OnboardingScreen.kt +++ b/modules/onboarding/src/main/kotlin/edu/stanford/spezi/module/onboarding/onboarding/OnboardingScreen.kt @@ -21,10 +21,14 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.hilt.navigation.compose.hiltViewModel import edu.stanford.spezi.core.design.theme.Colors.primary import edu.stanford.spezi.core.design.theme.Sizes import edu.stanford.spezi.core.design.theme.Spacings +import edu.stanford.spezi.core.design.theme.SpeziTheme import edu.stanford.spezi.core.design.theme.TextStyles.bodyLarge import edu.stanford.spezi.core.design.theme.TextStyles.bodyMedium import edu.stanford.spezi.core.design.theme.TextStyles.titleLarge @@ -107,7 +111,10 @@ fun FeatureItem(area: Area) { Column { Text( text = area.title, - modifier = Modifier.testIdentifier(OnboardingScreenTestIdentifier.AREA_TITLE, area.title), + modifier = Modifier.testIdentifier( + OnboardingScreenTestIdentifier.AREA_TITLE, + area.title + ), style = titleSmall ) Text( @@ -118,6 +125,47 @@ fun FeatureItem(area: Area) { } } +private class OnboardingUiStateProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + OnboardingUiState( + title = "Welcome", + subtitle = "Onboarding Subtitle", + areas = listOf( + Area( + title = "Area 1 Title", + description = "This is a description for area 1. Those descriptions are very important and can have different lengths.", + iconId = edu.stanford.spezi.core.design.R.drawable.ic_assignment + ), + Area( + title = "Area 2 Title", + description = "Short descriptions are also possible.", + iconId = edu.stanford.spezi.core.design.R.drawable.ic_bluetooth + ), + Area( + title = "Area 3 title", + description = "The colors on the screen are from the Spezi theme and if the user has dark mode or dynamic colors enabled, the colors will change accordingly.", + iconId = edu.stanford.spezi.core.design.R.drawable.ic_vital_signs + ), + ), + continueButtonText = "Continue" + ), + ) +} + +@Preview(showBackground = true, heightDp = 600, widthDp = 300) +@Composable +private fun OnboardingScreenPreview( + @PreviewParameter(OnboardingUiStateProvider::class) uiState: OnboardingUiState, +) { + SpeziTheme { + OnboardingScreen( + uiState = uiState, + onAction = { } + ) + } +} + + enum class OnboardingScreenTestIdentifier { ROOT, TITLE, From e41e638628cda84529b603d891dedcfa3636f5ba Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:11:07 +0200 Subject: [PATCH 02/80] added app screen navigation event Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/navigation/AppNavigationEvent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/AppNavigationEvent.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/AppNavigationEvent.kt index 38c94f320..d742060c8 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/AppNavigationEvent.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/AppNavigationEvent.kt @@ -3,5 +3,5 @@ package edu.stanford.bdh.engagehf.navigation import edu.stanford.spezi.core.navigation.NavigationEvent sealed interface AppNavigationEvent : NavigationEvent { - data object BluetoothScreen : AppNavigationEvent + data object AppScreen : AppNavigationEvent } From f6d30588bb414580ad05fdeca57fd33e0fb9b3cc Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:12:20 +0200 Subject: [PATCH 03/80] added Route to App Screen and Video Detail Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/navigation/Routes.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/Routes.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/Routes.kt index 0f8ed1dc5..a76818408 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/Routes.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/Routes.kt @@ -13,6 +13,12 @@ sealed class Routes { @Serializable data class LoginScreen(val isAlreadyRegistered: @Serializable Boolean = true) : Routes() + @Serializable + data class VideoDetail(val videoParams: @Serializable VideoParams) : Routes() + + @Serializable + data object AppScreen : Routes() + @Serializable data object SequentialOnboardingScreen : Routes() @@ -24,10 +30,10 @@ sealed class Routes { @Serializable data object ConsentScreen : Routes() - - @Serializable - data object BluetoothScreen : Routes() } @Serializable data class RegisterParams(val isGoogleSignUp: Boolean, val email: String, val password: String) + +@Serializable +data class VideoParams(val youtubeId: String, val title: String) From 07c946b2bd33bb371c38f12b9e65bf29567fb72c Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:12:49 +0200 Subject: [PATCH 04/80] updated onConsented Target Signed-off-by: Basler182 --- .../stanford/bdh/engagehf/onboarding/EngageConsentManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/onboarding/EngageConsentManager.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/onboarding/EngageConsentManager.kt index 0cdfedfbd..93b3cd583 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/onboarding/EngageConsentManager.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/onboarding/EngageConsentManager.kt @@ -26,7 +26,7 @@ class EngageConsentManager @Inject internal constructor( override suspend fun onConsented(uiState: ConsentUiState): Result = runCatching { val pdfBytes = pdfCreationService.createPdf(uiState) if (pdfService.uploadPdf(pdfBytes).getOrThrow()) { - navigator.navigateTo(AppNavigationEvent.BluetoothScreen) + navigator.navigateTo(AppNavigationEvent.AppScreen) } else { error("Upload went wrong") } From dd45165c4b7f8e9098c485d4e530147503e69d08 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:15:01 +0200 Subject: [PATCH 05/80] changed to elevated card Signed-off-by: Basler182 --- .../spezi/modules/contact/component/ContactOptionCard.kt | 4 ++-- .../spezi/modules/contact/component/NavigationCard.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/ContactOptionCard.kt b/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/ContactOptionCard.kt index 9546dbcff..a5475d810 100644 --- a/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/ContactOptionCard.kt +++ b/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/ContactOptionCard.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Email -import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -33,7 +33,7 @@ import java.util.UUID */ @Composable fun ContactOptionCard(option: ContactOption, publisher: (OnAction) -> Unit) { - Card( + ElevatedCard( modifier = Modifier .wrapContentSize(Alignment.Center) .width(90.dp) diff --git a/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/NavigationCard.kt b/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/NavigationCard.kt index bbfcca4ad..ad38029dc 100644 --- a/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/NavigationCard.kt +++ b/modules/contact/src/main/kotlin/edu/stanford/spezi/modules/contact/component/NavigationCard.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Place -import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -29,7 +29,7 @@ import edu.stanford.spezi.modules.contact.OnAction */ @Composable fun NavigationCard(address: String, publisher: (OnAction) -> Unit) { - Card( + ElevatedCard( modifier = Modifier .fillMaxWidth() ) { From 39eeb0ed81b1f809a617d36d4561fa80da2363c0 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:15:44 +0200 Subject: [PATCH 06/80] added gitignore Signed-off-by: Basler182 --- modules/education/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 modules/education/.gitignore diff --git a/modules/education/.gitignore b/modules/education/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/modules/education/.gitignore @@ -0,0 +1 @@ +/build From c7e89a0d148e0ba4a1753fcd926f15b80bd86e74 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:16:05 +0200 Subject: [PATCH 07/80] added education module Signed-off-by: Basler182 --- settings.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index c162c0d52..eff63b158 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,3 +40,4 @@ include(":modules:account") include(":modules:contact") include(":modules:healthconnectonfhir") include(":modules:onboarding") +include(":modules:education") From 3d3d23212aba87f842b9aab02fd934a7c60a4766 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:20:21 +0200 Subject: [PATCH 08/80] added NavigationItem Signed-off-by: Basler182 --- .../bdh/engagehf/navigation/NavigationItem.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/NavigationItem.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/NavigationItem.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/NavigationItem.kt new file mode 100644 index 000000000..94ea32c2f --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/NavigationItem.kt @@ -0,0 +1,13 @@ +package edu.stanford.bdh.engagehf.navigation + +import androidx.annotation.DrawableRes +import edu.stanford.bdh.engagehf.NavigationItemEnum + +data class NavigationItem( + @DrawableRes val icon: Int, + @DrawableRes val selectedIcon: Int, + val label: String, + var selected: Boolean = false, + val onClick: () -> Unit = {}, + val navigationItem: NavigationItemEnum, +) From 63747f3316eb350b64ea15ba0120da07d48c8b26 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 28 Jun 2024 23:46:53 +0200 Subject: [PATCH 09/80] added engage education repo and mapper Signed-off-by: Basler182 --- .../bdh/engagehf/education/EducationModul.kt | 16 ++++++ .../education/EngageEducationRepository.kt | 20 +++++++ ...ideoSectionDocumentToVideoSectionMapper.kt | 55 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt new file mode 100644 index 000000000..7ca0597e0 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt @@ -0,0 +1,16 @@ +package edu.stanford.bdh.engagehf.education + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import edu.stanford.spezi.modules.education.EducationRepository + +@Module +@InstallIn(SingletonComponent::class) +abstract class EducationModule { + @Binds + abstract fun bindEducationRepository( + engageEducationRepository: EngageEducationRepository, + ): EducationRepository +} diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt new file mode 100644 index 000000000..b43a6a779 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt @@ -0,0 +1,20 @@ +package edu.stanford.bdh.engagehf.education + +import com.google.firebase.firestore.FirebaseFirestore +import edu.stanford.spezi.module.onboarding.invitation.await +import edu.stanford.spezi.modules.education.EducationRepository +import edu.stanford.spezi.modules.education.VideoSection +import javax.inject.Inject + +class EngageEducationRepository @Inject constructor( + private val firebaseFirestore: FirebaseFirestore, + private val mapper: VideoSectionDocumentToVideoSectionMapper, +) : EducationRepository { + override suspend fun getVideoSections(language: String): Result> { + return runCatching { + firebaseFirestore.collection("videoSections").get().await().map { document -> + mapper.map(document, language) + }.sortedBy { it.orderIndex } + } + } +} diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt new file mode 100644 index 000000000..8e4187082 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt @@ -0,0 +1,55 @@ +package edu.stanford.bdh.engagehf.education + +import com.google.firebase.firestore.DocumentSnapshot +import edu.stanford.spezi.module.onboarding.invitation.await +import edu.stanford.spezi.modules.education.Video +import edu.stanford.spezi.modules.education.VideoSection +import javax.inject.Inject + +class VideoSectionDocumentToVideoSectionMapper @Inject constructor() { + + suspend fun map(document: DocumentSnapshot, language: String): VideoSection { + val title = getLocalizedString(document, "title", language) + val orderIndex = document.getLong("orderIndex")?.toInt() ?: 0 + val description = + getLocalizedString(document, "description", language) + + val videosResult = document.reference.collection("videos").get().await() + + val videoList = videosResult.map { videoDocument -> + mapVideo(videoDocument, language) + }.sortedBy { it.orderIndex } + + return VideoSection( + title = title, + description = description, + orderIndex = orderIndex, + videos = videoList + ) + } + + private fun mapVideo(document: DocumentSnapshot, language: String): Video { + val videoTitle = getLocalizedString(document, "title", language) + val videoDescription = getLocalizedString(document, "description", language) + + return Video( + title = videoTitle, + description = videoDescription, + orderIndex = document.get("orderIndex") as? Int ?: 0, + youtubeId = document.get("youtubeId") as? String + ) + } + + private fun getLocalizedString( + document: DocumentSnapshot, + field: String, + language: String, + ): String { + val fieldContent = document.get(field) + return if (fieldContent is Map<*, *>) { + fieldContent[language] as? String ?: "" + } else { + fieldContent as? String ?: "" + } + } +} From a5e2f21f94b53a2ecbf4812dca151ad7e901c2db Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:02:56 +0200 Subject: [PATCH 10/80] added app screen Signed-off-by: Basler182 --- .../engagehf/navigation/screens/AppScreen.kt | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt new file mode 100644 index 000000000..a48d0ee11 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt @@ -0,0 +1,96 @@ +package edu.stanford.bdh.engagehf.navigation.screens + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import edu.stanford.bdh.engagehf.Action +import edu.stanford.bdh.engagehf.MainActivityViewModel +import edu.stanford.bdh.engagehf.NavigationItemEnum +import edu.stanford.bdh.engagehf.UiState +import edu.stanford.bdh.engagehf.bluetooth.screen.BluetoothScreen +import edu.stanford.spezi.core.design.component.AppTopAppBar +import edu.stanford.spezi.modules.education.EducationScreen + +@Composable +fun AppScreen() { + val viewModel = hiltViewModel() + val uiState by viewModel.uiState.collectAsState() + AppScreen( + uiState = uiState, + onAction = viewModel::onAction + ) +} + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable +fun AppScreen( + uiState: UiState, + onAction: (Action) -> Unit, +) { + Scaffold( + topBar = { + AppTopAppBar(title = uiState.navigationItems[uiState.selectedIndex].label) + }, + bottomBar = { + Column { + HorizontalDivider( + thickness = 0.5.dp, + ) + NavigationBar( + tonalElevation = (-1).dp, + ) { + uiState.navigationItems.forEachIndexed { index, item -> + NavigationBarItem( + icon = { + Icon( + painter = painterResource(id = item.icon), + contentDescription = null + ) + }, + label = { Text(text = item.label) }, + selected = item.selected, + onClick = { + onAction(Action.UpdateSelectedIndex(index)) + }, + ) + } + } + } + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + when (uiState.navigationItems[uiState.selectedIndex].navigationItem) { + NavigationItemEnum.Home -> { + BluetoothScreen() + } + + NavigationItemEnum.HeartHealth -> { + BluetoothScreen() + } + + NavigationItemEnum.Medication -> { + EducationScreen() + } + + NavigationItemEnum.Education -> { + EducationScreen() + } + } + } + } +} From edfbdb44782d18c8f81ba206e4b7920f75b68c41 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:04:25 +0200 Subject: [PATCH 11/80] updated main activity Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/MainActivity.kt | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt index da89bcbf2..d2103b798 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt @@ -13,10 +13,11 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute import dagger.hilt.android.AndroidEntryPoint -import edu.stanford.bdh.engagehf.bluetooth.screen.BluetoothScreen import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent import edu.stanford.bdh.engagehf.navigation.RegisterParams import edu.stanford.bdh.engagehf.navigation.Routes +import edu.stanford.bdh.engagehf.navigation.VideoParams +import edu.stanford.bdh.engagehf.navigation.screens.AppScreen import edu.stanford.bdh.engagehf.navigation.serializableType import edu.stanford.spezi.core.coroutines.di.Dispatching import edu.stanford.spezi.core.design.theme.SpeziTheme @@ -28,6 +29,9 @@ import edu.stanford.spezi.module.onboarding.consent.ConsentScreen import edu.stanford.spezi.module.onboarding.invitation.InvitationCodeScreen import edu.stanford.spezi.module.onboarding.onboarding.OnboardingScreen import edu.stanford.spezi.module.onboarding.sequential.SequentialOnboardingScreen +import edu.stanford.spezi.modules.education.EducationNavigationEvent +import edu.stanford.spezi.modules.education.EducationNavigationEvents +import edu.stanford.spezi.modules.education.video.VideoScreen import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.launch import javax.inject.Inject @@ -82,8 +86,17 @@ class MainActivity : ComponentActivity() { LoginScreen(isAlreadyRegistered = args.isAlreadyRegistered) } - composable { - BluetoothScreen() + composable( + typeMap = mapOf( + typeOf() to serializableType() + ) + ) { + val args = it.toRoute() + VideoScreen(videoId = args.videoParams.youtubeId, videoTitle = args.videoParams.title) + } + + composable { + AppScreen() } composable { @@ -125,7 +138,6 @@ class MainActivity : ComponentActivity() { ) ) - is AppNavigationEvent.BluetoothScreen -> navHostController.navigate(Routes.BluetoothScreen) is OnboardingNavigationEvent.InvitationCodeScreen -> navHostController.navigate( Routes.InvitationCodeScreen ) @@ -141,6 +153,17 @@ class MainActivity : ComponentActivity() { is OnboardingNavigationEvent.ConsentScreen -> navHostController.navigate( Routes.ConsentScreen ) + + is AppNavigationEvent.AppScreen -> navHostController.navigate(Routes.AppScreen) + is EducationNavigationEvents.PopUp -> navHostController.popBackStack() + + is EducationNavigationEvent.VideoSectionClicked -> navHostController.navigate( + Routes.VideoDetail( + videoParams = VideoParams( + youtubeId = event.youtubeId, title = event.title + ) + ) + ) } } } From d8489fb0d4434f591f58d054f3e7d6bd8a869256 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:12:18 +0200 Subject: [PATCH 12/80] added education to build.gradle.kts Signed-off-by: Basler182 --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c4fe021ab..799b0f2a5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(project(":core:coroutines")) implementation(project(":core:navigation")) implementation(project(":modules:account")) + implementation(project(":modules:education")) implementation(project(":modules:onboarding")) implementation(libs.androidx.core.ktx) From 07a3a0a35854654f09eb56ee9dbe71b02b175926 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:12:37 +0200 Subject: [PATCH 13/80] added home drawable Signed-off-by: Basler182 --- core/design/src/main/res/drawable/ic_home.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 core/design/src/main/res/drawable/ic_home.xml diff --git a/core/design/src/main/res/drawable/ic_home.xml b/core/design/src/main/res/drawable/ic_home.xml new file mode 100644 index 000000000..c80a5d89f --- /dev/null +++ b/core/design/src/main/res/drawable/ic_home.xml @@ -0,0 +1,10 @@ + + + From 1a4dca72bd5590f8830f386cbfb1e8d97c94bbf1 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:17:47 +0200 Subject: [PATCH 14/80] app ui state Signed-off-by: Basler182 --- .../navigation/data/models/AppUiState.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/data/models/AppUiState.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/data/models/AppUiState.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/data/models/AppUiState.kt new file mode 100644 index 000000000..bfd07e175 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/data/models/AppUiState.kt @@ -0,0 +1,17 @@ +package edu.stanford.bdh.engagehf.navigation.data.models + +import edu.stanford.bdh.engagehf.education +import edu.stanford.bdh.engagehf.heartHealth +import edu.stanford.bdh.engagehf.home +import edu.stanford.bdh.engagehf.medication +import edu.stanford.bdh.engagehf.navigation.NavigationItem + +data class AppUiState( + val selectedIndex: Int = 0, + val navigationItems: List = listOf( + home, + heartHealth, + medication, + education, + ), +) From 87982aa6300127855cd51d865256de2b7fd97474 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:18:02 +0200 Subject: [PATCH 15/80] updated app screen Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt index a48d0ee11..575df6cf9 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt @@ -19,8 +19,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import edu.stanford.bdh.engagehf.Action import edu.stanford.bdh.engagehf.MainActivityViewModel import edu.stanford.bdh.engagehf.NavigationItemEnum -import edu.stanford.bdh.engagehf.UiState import edu.stanford.bdh.engagehf.bluetooth.screen.BluetoothScreen +import edu.stanford.bdh.engagehf.navigation.data.models.AppUiState import edu.stanford.spezi.core.design.component.AppTopAppBar import edu.stanford.spezi.modules.education.EducationScreen @@ -37,7 +37,7 @@ fun AppScreen() { @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun AppScreen( - uiState: UiState, + uiState: AppUiState, onAction: (Action) -> Unit, ) { Scaffold( From bff7499a26ca80f1fc918e87a5fc4181fae1490b Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:18:33 +0200 Subject: [PATCH 16/80] updated MainActivityViewModel Signed-off-by: Basler182 --- .../bdh/engagehf/MainActivityViewModel.kt | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModel.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModel.kt index 16c1b731c..1fe4b18c2 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModel.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModel.kt @@ -4,11 +4,17 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent +import edu.stanford.bdh.engagehf.navigation.NavigationItem +import edu.stanford.bdh.engagehf.navigation.data.models.AppUiState +import edu.stanford.spezi.core.design.R import edu.stanford.spezi.core.logging.speziLogger import edu.stanford.spezi.core.navigation.NavigationEvent import edu.stanford.spezi.core.navigation.Navigator import edu.stanford.spezi.module.account.AccountEvents import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -19,13 +25,17 @@ class MainActivityViewModel @Inject constructor( ) : ViewModel() { private val logger by speziLogger() + private val _uiState = MutableStateFlow(AppUiState()) + val uiState = _uiState.asStateFlow() + init { viewModelScope.launch { accountEvents.events.collect { event -> when (event) { is AccountEvents.Event.SignUpSuccess, AccountEvents.Event.SignInSuccess -> { - navigator.navigateTo(AppNavigationEvent.BluetoothScreen) + navigator.navigateTo(AppNavigationEvent.AppScreen) } + else -> { logger.i { "Ignoring registration event: $event" } } @@ -34,5 +44,59 @@ class MainActivityViewModel @Inject constructor( } } + fun onAction(action: Action) { + when (action) { + is Action.UpdateSelectedIndex -> { + _uiState.update { + it.copy(selectedIndex = action.index, + navigationItems = it.navigationItems.mapIndexed { index, item -> + item.copy(selected = index == action.index) + } + ) + } + } + } + } + fun getNavigationEvents(): Flow = navigator.events } + +sealed interface Action { + data class UpdateSelectedIndex(val index: Int) : Action +} + +val home = NavigationItem( + icon = R.drawable.ic_home, + selectedIcon = R.drawable.ic_home, + label = "Home", + navigationItem = NavigationItemEnum.Home, + selected = true, +) + +val heartHealth = NavigationItem( + icon = R.drawable.ic_vital_signs, + selectedIcon = R.drawable.ic_vital_signs, + label = "Heart Health", + navigationItem = NavigationItemEnum.HeartHealth, +) + +val medication = NavigationItem( + icon = R.drawable.ic_medication, + selectedIcon = R.drawable.ic_medication, + label = "Medication", + navigationItem = NavigationItemEnum.Medication, +) + +val education = NavigationItem( + icon = R.drawable.ic_school, + selectedIcon = R.drawable.ic_school, + label = "Education", + navigationItem = NavigationItemEnum.Education, +) + +enum class NavigationItemEnum { + Home, + HeartHealth, + Medication, + Education, +} From 19fa7b5cfb0ffe3ef4e56a8d6685478e1eb2d94f Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:19:48 +0200 Subject: [PATCH 17/80] tested MainActivityViewModel Signed-off-by: Basler182 --- .../bdh/engagehf/MainActivityViewModelTest.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 app/src/test/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModelTest.kt diff --git a/app/src/test/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModelTest.kt b/app/src/test/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModelTest.kt new file mode 100644 index 000000000..f524004d4 --- /dev/null +++ b/app/src/test/kotlin/edu/stanford/bdh/engagehf/MainActivityViewModelTest.kt @@ -0,0 +1,44 @@ +package edu.stanford.bdh.engagehf + +import com.google.common.truth.Truth.assertThat +import edu.stanford.spezi.core.navigation.Navigator +import edu.stanford.spezi.core.testing.CoroutineTestRule +import edu.stanford.spezi.core.testing.runTestUnconfined +import edu.stanford.spezi.module.account.AccountEvents +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class MainActivityViewModelTest { + + private var mockAccountEvents: AccountEvents = mockk(relaxed = true) + private var mockNavigator: Navigator = mockk(relaxed = true) + + private lateinit var viewModel: MainActivityViewModel + + @get:Rule + val coroutineTestRule = CoroutineTestRule() + + @Before + fun setup() { + viewModel = MainActivityViewModel(mockAccountEvents, mockNavigator) + } + + @Test + fun `given selectedIndex when onAction UpdateSelectedIndex then uiState selectedIndex should be updated`() = + runTestUnconfined { + // Given + val initialIndex = viewModel.uiState.value.selectedIndex + val newIndex = initialIndex + 1 + + // When + viewModel.onAction(Action.UpdateSelectedIndex(newIndex)) + + // Then + val updatedIndex = viewModel.uiState.value.selectedIndex + assertThat(updatedIndex).isEqualTo(newIndex) + } +} From e4c7b78fb18a7400a671b49a5e3422f682dcb158 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 00:20:21 +0200 Subject: [PATCH 18/80] added top app bar Signed-off-by: Basler182 --- .../spezi/core/design/component/TopAppBar.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/TopAppBar.kt diff --git a/core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/TopAppBar.kt b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/TopAppBar.kt new file mode 100644 index 000000000..78c991bc6 --- /dev/null +++ b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/component/TopAppBar.kt @@ -0,0 +1,24 @@ +package edu.stanford.spezi.core.design.component + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import edu.stanford.spezi.core.design.theme.Colors.onPrimary +import edu.stanford.spezi.core.design.theme.Colors.primary + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppTopAppBar(title: String) { + TopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = primary, + scrolledContainerColor = primary, + navigationIconContentColor = onPrimary, + titleContentColor = onPrimary, + actionIconContentColor = onPrimary + ), + title = { Text(text = title) } + ) +} From 79335a5624faa33fe549dbdaffc7ca41a18908e7 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sat, 29 Jun 2024 10:38:49 +0200 Subject: [PATCH 19/80] added video screen Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/MainActivity.kt | 3 +- .../bdh/engagehf/education/EducationModul.kt | 2 +- .../education/EngageEducationRepository.kt | 4 +- ...ideoSectionDocumentToVideoSectionMapper.kt | 4 +- .../engagehf/navigation/screens/AppScreen.kt | 2 +- .../spezi/core/design/theme/Elevations.kt | 25 +++ .../spezi/core/design/theme/Shapes.kt | 26 +++ .../modules/education/video/VideoScreen.kt | 72 +++++++ .../modules/education/video/VideoViewModel.kt | 25 +++ .../videos/component/ExpandableSection.kt | 195 ++++++++++++++++++ .../data/repository/EducationRepository.kt | 7 + 11 files changed, 357 insertions(+), 8 deletions(-) create mode 100644 core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Elevations.kt create mode 100644 core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Shapes.kt create mode 100644 modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoScreen.kt create mode 100644 modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoViewModel.kt create mode 100644 modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/component/ExpandableSection.kt create mode 100644 modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/data/repository/EducationRepository.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt index d2103b798..1bc77f25e 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/MainActivity.kt @@ -30,7 +30,6 @@ import edu.stanford.spezi.module.onboarding.invitation.InvitationCodeScreen import edu.stanford.spezi.module.onboarding.onboarding.OnboardingScreen import edu.stanford.spezi.module.onboarding.sequential.SequentialOnboardingScreen import edu.stanford.spezi.modules.education.EducationNavigationEvent -import edu.stanford.spezi.modules.education.EducationNavigationEvents import edu.stanford.spezi.modules.education.video.VideoScreen import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.launch @@ -155,7 +154,7 @@ class MainActivity : ComponentActivity() { ) is AppNavigationEvent.AppScreen -> navHostController.navigate(Routes.AppScreen) - is EducationNavigationEvents.PopUp -> navHostController.popBackStack() + is EducationNavigationEvent.PopUp -> navHostController.popBackStack() is EducationNavigationEvent.VideoSectionClicked -> navHostController.navigate( Routes.VideoDetail( diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt index 7ca0597e0..f5bc263fd 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EducationModul.kt @@ -4,7 +4,7 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import edu.stanford.spezi.modules.education.EducationRepository +import edu.stanford.spezi.modules.education.videos.data.repository.EducationRepository @Module @InstallIn(SingletonComponent::class) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt index b43a6a779..a74b06b9e 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/EngageEducationRepository.kt @@ -2,8 +2,8 @@ package edu.stanford.bdh.engagehf.education import com.google.firebase.firestore.FirebaseFirestore import edu.stanford.spezi.module.onboarding.invitation.await -import edu.stanford.spezi.modules.education.EducationRepository -import edu.stanford.spezi.modules.education.VideoSection +import edu.stanford.spezi.modules.education.videos.VideoSection +import edu.stanford.spezi.modules.education.videos.data.repository.EducationRepository import javax.inject.Inject class EngageEducationRepository @Inject constructor( diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt index 8e4187082..9c5b4a958 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/education/VideoSectionDocumentToVideoSectionMapper.kt @@ -2,8 +2,8 @@ package edu.stanford.bdh.engagehf.education import com.google.firebase.firestore.DocumentSnapshot import edu.stanford.spezi.module.onboarding.invitation.await -import edu.stanford.spezi.modules.education.Video -import edu.stanford.spezi.modules.education.VideoSection +import edu.stanford.spezi.modules.education.videos.Video +import edu.stanford.spezi.modules.education.videos.VideoSection import javax.inject.Inject class VideoSectionDocumentToVideoSectionMapper @Inject constructor() { diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt index 575df6cf9..f41cbf1a7 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt @@ -22,7 +22,7 @@ import edu.stanford.bdh.engagehf.NavigationItemEnum import edu.stanford.bdh.engagehf.bluetooth.screen.BluetoothScreen import edu.stanford.bdh.engagehf.navigation.data.models.AppUiState import edu.stanford.spezi.core.design.component.AppTopAppBar -import edu.stanford.spezi.modules.education.EducationScreen +import edu.stanford.spezi.modules.education.videos.EducationScreen @Composable fun AppScreen() { diff --git a/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Elevations.kt b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Elevations.kt new file mode 100644 index 000000000..d16689089 --- /dev/null +++ b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Elevations.kt @@ -0,0 +1,25 @@ +package edu.stanford.spezi.core.design.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +object Elevations { + object Card { + val small: Dp + @Composable + @ReadOnlyComposable + get() = 2.dp + + val medium: Dp + @Composable + @ReadOnlyComposable + get() = 4.dp + + val large: Dp + @Composable + @ReadOnlyComposable + get() = 8.dp + } +} diff --git a/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Shapes.kt b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Shapes.kt new file mode 100644 index 000000000..fed1fc577 --- /dev/null +++ b/core/design/src/main/kotlin/edu/stanford/spezi/core/design/theme/Shapes.kt @@ -0,0 +1,26 @@ +package edu.stanford.spezi.core.design.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +object Shapes { + + object RoundedCorners { + val small: Dp + @Composable + @ReadOnlyComposable + get() = 2.dp + + val medium: Dp + @Composable + @ReadOnlyComposable + get() = 4.dp + + val large: Dp + @Composable + @ReadOnlyComposable + get() = 8.dp + } +} diff --git a/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoScreen.kt b/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoScreen.kt new file mode 100644 index 000000000..c7830a974 --- /dev/null +++ b/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoScreen.kt @@ -0,0 +1,72 @@ +package edu.stanford.spezi.modules.education.video + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.hilt.navigation.compose.hiltViewModel +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView +import edu.stanford.spezi.core.design.theme.Colors.onPrimary +import edu.stanford.spezi.core.design.theme.Colors.primary + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun VideoScreen(videoId: String?, videoTitle: String) { + val viewModel = hiltViewModel() + val context = LocalContext.current + + Scaffold( + topBar = { + TopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = primary, + scrolledContainerColor = primary, + navigationIconContentColor = onPrimary, + titleContentColor = onPrimary, + actionIconContentColor = onPrimary + ), + title = { Text(text = videoTitle) }, + navigationIcon = { + IconButton(onClick = { + viewModel.onAction(Action.BackPressed) + }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + } + ) + }, + content = { paddingValues -> + Column( + modifier = Modifier.padding(paddingValues) + ) { + AndroidView(factory = { + YouTubePlayerView(context).apply { + addYouTubePlayerListener(object : AbstractYouTubePlayerListener() { + override fun onReady(youTubePlayer: YouTubePlayer) { + videoId?.let { it1 -> youTubePlayer.cueVideo(it1, 0f) } + } + }) + } + }, update = { + // Any updates to the player view if needed + }, + modifier = Modifier.padding(16.dp) + ) + } + } + ) +} diff --git a/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoViewModel.kt b/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoViewModel.kt new file mode 100644 index 000000000..519f91a87 --- /dev/null +++ b/modules/education/src/main/java/edu/stanford/spezi/modules/education/video/VideoViewModel.kt @@ -0,0 +1,25 @@ +package edu.stanford.spezi.modules.education.video + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import edu.stanford.spezi.core.navigation.Navigator +import edu.stanford.spezi.modules.education.EducationNavigationEvent +import javax.inject.Inject + +@HiltViewModel +class VideoViewModel @Inject internal constructor( + private val navigator: Navigator, +) : ViewModel() { + + fun onAction(action: Action) { + when (action) { + is Action.BackPressed -> { + navigator.navigateTo(EducationNavigationEvent.PopUp) + } + } + } +} + +sealed class Action { + data object BackPressed : Action() +} diff --git a/modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/component/ExpandableSection.kt b/modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/component/ExpandableSection.kt new file mode 100644 index 000000000..5c0eb50c5 --- /dev/null +++ b/modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/component/ExpandableSection.kt @@ -0,0 +1,195 @@ +package edu.stanford.spezi.modules.education.videos.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import edu.stanford.spezi.core.design.theme.Colors +import edu.stanford.spezi.core.design.theme.Elevations +import edu.stanford.spezi.core.design.theme.Shapes +import edu.stanford.spezi.core.design.theme.Spacings +import edu.stanford.spezi.core.design.theme.SpeziTheme +import edu.stanford.spezi.core.design.theme.TextStyles +import edu.stanford.spezi.core.design.theme.TextStyles.titleLarge +import edu.stanford.spezi.modules.education.videos.Video +import edu.stanford.spezi.modules.education.videos.VideoItem + +@Composable +fun SectionHeader( + text: String?, + isExpanded: Boolean, + onHeaderClicked: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onHeaderClicked() } + .padding(Spacings.medium), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + text?.let { + Text( + text = it, + style = titleLarge + ) + } + + Icon( + imageVector = getExpandIcon(isExpanded), + contentDescription = if (isExpanded) "Collapse" else "Expand" + ) + } +} + +@Composable +fun ExpandableSection( + title: String?, + description: String?, + videos: List