diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/di/ViewModelModule.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/di/ViewModelModule.kt index b7fb145c..eb943ef7 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/di/ViewModelModule.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/di/ViewModelModule.kt @@ -1,6 +1,7 @@ package fr.paug.androidmakers.wear.di import fr.paug.androidmakers.wear.ui.main.MainViewModel +import fr.paug.androidmakers.wear.ui.session.details.SessionDetailViewModel import fr.paug.androidmakers.wear.ui.settings.SettingsViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -8,4 +9,5 @@ import org.koin.dsl.module val androidViewModelModule = module { viewModel { MainViewModel(get(), get(), get(), get(), get(), get()) } viewModel { SettingsViewModel(get(), get()) } + viewModel { SessionDetailViewModel(get(), get(), get(), get(), get(), get()) } } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/Loading.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/Loading.kt new file mode 100644 index 00000000..0e16e10a --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/common/Loading.kt @@ -0,0 +1,19 @@ +package fr.paug.androidmakers.wear.ui.common + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.wear.compose.material.CircularProgressIndicator + +@Composable +fun Loading() { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainActivity.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainActivity.kt index 789818bf..28736d8c 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainActivity.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainActivity.kt @@ -7,34 +7,47 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.core.app.ActivityCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.navigation.NavType +import androidx.navigation.navArgument import androidx.wear.compose.foundation.SwipeToDismissBoxState +import androidx.wear.compose.foundation.SwipeToDismissValue import androidx.wear.compose.foundation.edgeSwipeToDismiss import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable +import androidx.wear.compose.navigation.currentBackStackEntryAsState import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState import com.google.android.horologist.compose.layout.AppScaffold import com.google.android.horologist.compose.pager.PagerScreen import fr.androidmakers.domain.model.User import fr.paug.androidmakers.wear.R +import fr.paug.androidmakers.wear.ui.session.UISession +import fr.paug.androidmakers.wear.ui.session.details.SessionDetailScreen +import fr.paug.androidmakers.wear.ui.session.details.SessionDetailViewModel import fr.paug.androidmakers.wear.ui.session.list.SessionListScreen import fr.paug.androidmakers.wear.ui.settings.SettingsScreen import fr.paug.androidmakers.wear.ui.signin.SignInScreen import fr.paug.androidmakers.wear.ui.theme.AndroidMakersWearTheme import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { installSplashScreen() - WearApp() + WearApp(onSwipeToDismissRootLevel = { ActivityCompat.finishAffinity(this) }) setTheme(android.R.style.Theme_DeviceDefault) } } @@ -43,39 +56,69 @@ class MainActivity : ComponentActivity() { @Composable fun WearApp( viewModel: MainViewModel = koinViewModel(), + onSwipeToDismissRootLevel: () -> Unit, ) { val navController = rememberSwipeDismissableNavController() + val swipeToDismissBoxState = rememberSwipeToDismissBoxState() + val swipeDismissableNavHostState = rememberSwipeDismissableNavHostState(swipeToDismissBoxState) + + // Workaround so swipe to dismiss works on the root level + // See https://slack-chats.kotlinlang.org/t/16230979/problem-changing-basicswipetodismiss-background-color-gt + var currentRoute by remember { mutableStateOf("") } + LaunchedEffect(swipeToDismissBoxState.currentValue) { + if (swipeToDismissBoxState.currentValue == SwipeToDismissValue.Dismissed) { + if (currentRoute == Navigation.main) { + onSwipeToDismissRootLevel() + } + } + } + val onSignInClick: () -> Unit = { - navController.navigate(Navigation.SIGN_IN) + navController.navigate(Navigation.signIn) } val onSignInDismissOrTimeout: () -> Unit = { navController.popBackStack() } + + navController.currentBackStackEntryAsState().value?.let { backStackEntry -> + currentRoute = backStackEntry.destination.route ?: "" + } + val onSignInSuccess = viewModel::onSignInSuccess AndroidMakersWearTheme { AppScaffold { - val swipeToDismissBoxState = rememberSwipeToDismissBoxState() - val swipeDismissableNavHostState = - rememberSwipeDismissableNavHostState(swipeToDismissBoxState) SwipeDismissableNavHost( navController = navController, - startDestination = Navigation.MAIN, + startDestination = Navigation.main, state = swipeDismissableNavHostState, ) { - composable(Navigation.MAIN) { + composable(Navigation.main) { MainScreen( + viewModel = viewModel, + swipeToDismissBoxState = swipeToDismissBoxState, onSignInClick = onSignInClick, onSignOutClick = { viewModel.signOut() }, - swipeToDismissBoxState = swipeToDismissBoxState, - viewModel = viewModel, + onSessionClick = { sessionId -> + navController.navigate(Navigation.sessionDetail(sessionId)) + } ) } - composable(Navigation.SIGN_IN) { + composable(Navigation.signIn) { SignInScreen( onSignInSuccess = onSignInSuccess, onDismissOrTimeout = onSignInDismissOrTimeout ) } + composable( + Navigation.sessionDetail, + arguments = listOf(navArgument(Navigation.id) { type = NavType.StringType }) + ) { + val sessionId = it.arguments!!.getString(Navigation.id)!! + val sessionDetailViewModel: SessionDetailViewModel = + koinViewModel { parametersOf(sessionId) } + + SessionDetailScreen(sessionDetailViewModel) + } } } } @@ -87,6 +130,7 @@ fun MainScreen( swipeToDismissBoxState: SwipeToDismissBoxState, onSignInClick: () -> Unit, onSignOutClick: () -> Unit, + onSessionClick: (String) -> Unit, ) { val pagerState: PagerState = rememberPagerState(initialPage = viewModel.getConferenceDay() + 1, pageCount = { 3 }) @@ -110,11 +154,19 @@ fun MainScreen( } 1 -> { - SessionListScreen(sessions = sessionsDay1, title = stringResource(id = R.string.main_day1)) + SessionListScreen( + sessions = sessionsDay1, + title = stringResource(id = R.string.main_day1), + onSessionClick = onSessionClick + ) } 2 -> { - SessionListScreen(sessions = sessionsDay2, title = stringResource(id = R.string.main_day2)) + SessionListScreen( + sessions = sessionsDay2, + title = stringResource(id = R.string.main_day2), + onSessionClick = onSessionClick + ) } } } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainViewModel.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainViewModel.kt index 46b01fa6..3e1b5d5c 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainViewModel.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/MainViewModel.kt @@ -17,6 +17,7 @@ import fr.androidmakers.domain.repo.UserRepository import fr.paug.androidmakers.wear.R import fr.paug.androidmakers.wear.applicationContext import fr.paug.androidmakers.wear.data.LocalPreferencesRepository +import fr.paug.androidmakers.wear.ui.session.UISession import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/Navigation.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/Navigation.kt index 318a8e75..44951bf3 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/Navigation.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/Navigation.kt @@ -1,6 +1,13 @@ package fr.paug.androidmakers.wear.ui.main object Navigation { - const val MAIN = "MAIN" - const val SIGN_IN = "SIGN_IN" + const val main = "main" + + const val signIn = "signIn" + + const val id = "id" + const val sessionDetail = "sessionDetail/{$id}" + fun sessionDetail(id: String): String { + return "sessionDetail/$id" + } } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/PreviewData.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/PreviewData.kt new file mode 100644 index 00000000..fc29ec12 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/PreviewData.kt @@ -0,0 +1,67 @@ +package fr.paug.androidmakers.wear.ui.session + +import fr.androidmakers.domain.model.Room +import fr.androidmakers.domain.model.Session +import fr.androidmakers.domain.model.Speaker +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.Month + +val uiSession1 = UISession( + session = Session( + id = "1", + title = "Android Graphics: the Path to [UI] Riches", + description = "Android's graphics APIs are extensive and powerful... but maybe a little complicated. This session will show ways to use the graphics APIs to achieve cool effects and improve the visual quality and richness of your applications.", + roomId = "", + speakers = emptyList(), + startsAt = LocalDateTime(2023, Month.APRIL, 27, 9, 15), + endsAt = LocalDateTime(2023, Month.APRIL, 27, 10, 0), + isServiceSession = false, + ), + speakers = listOf( + Speaker( + id = "1", + name = "Speaker 1", + bio = "Bio 1", + ), + Speaker( + id = "2", + name = "Speaker 2", + bio = "Bio 2", + ) + ), + room = Room( + id = "1", + name = "Room 1" + ), + isBookmarked = true, +) + +val uiSession2 = UISession( + session = Session( + id = "2", + title = "Using Compose Runtime to create a client library", + description = "Jetpack Compose (UI) is a powerful UI toolkit for Android. Have you ever wondered where this power comes from? The answer is Compose Runtime. \r\n\r\nIn this talk, we will see how we can use Compose Runtime to create client libraries. Firstly, we will talk about Compose nodes, Composition, Recomposer, and how they are orchestrated to create a slot table. Then, we will see how the changes in the slot table are applied with an Applier. Moreover, we will touch upon the Snapshot system and how the changes in the state objects trigger a recomposition. Finally, we will create a basic UI toolkit for PowerPoint using Compose Runtime.", + roomId = "", + speakers = emptyList(), + startsAt = LocalDateTime(2023, Month.APRIL, 27, 10, 15), + endsAt = LocalDateTime(2023, Month.APRIL, 27, 11, 0), + isServiceSession = false, + ), + speakers = listOf( + Speaker( + id = "3", + name = "Speaker 3", + bio = "Bio 3", + ), + ), + room = Room( + id = "2", + name = "Room 2" + ), + isBookmarked = false, +) + +val uiSessions = listOf( + uiSession1, + uiSession2, +) diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/UISession.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt similarity index 88% rename from wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/UISession.kt rename to wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt index b5e192c1..636a1743 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/UISession.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/UISession.kt @@ -1,4 +1,4 @@ -package fr.paug.androidmakers.wear.ui.main +package fr.paug.androidmakers.wear.ui.session import fr.androidmakers.domain.model.Room import fr.androidmakers.domain.model.Session diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt new file mode 100644 index 00000000..8fc2a790 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailScreen.kt @@ -0,0 +1,183 @@ +@file:OptIn(ExperimentalHorologistApi::class) + +package fr.paug.androidmakers.wear.ui.session.details + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Bookmark +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.Hyphens +import androidx.compose.ui.text.style.LineBreak +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.LocalContentColor +import androidx.wear.compose.material.LocalTextStyle +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices +import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumn +import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults +import com.google.android.horologist.compose.layout.ScreenScaffold +import com.google.android.horologist.compose.layout.rememberResponsiveColumnState +import fr.paug.androidmakers.wear.ui.common.Loading +import fr.paug.androidmakers.wear.ui.session.UISession +import fr.paug.androidmakers.wear.ui.session.uiSession1 +import fr.paug.androidmakers.wear.ui.theme.amRed + +@Composable +fun SessionDetailScreen(viewModel: SessionDetailViewModel) { + val session: UISession? by viewModel.uiSession.collectAsState(null) + if (session == null) { + Loading() + } else { + Session(session!!) + } +} + +@Composable +private fun Session(session: UISession) { + val columnState = rememberResponsiveColumnState( + contentPadding = ScalingLazyColumnDefaults.padding( + first = ScalingLazyColumnDefaults.ItemType.Text, + last = ScalingLazyColumnDefaults.ItemType.Text, + ) + ) + ScreenScaffold(scrollState = columnState) { + ScalingLazyColumn( + columnState = columnState, + modifier = Modifier.fillMaxSize() + ) { + item { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colors.onSurfaceVariant, + LocalTextStyle provides MaterialTheme.typography.caption1, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (session.isBookmarked) { + Icon( + modifier = Modifier.size(18.dp), + imageVector = Icons.Rounded.Bookmark, + tint = amRed, + contentDescription = "Bookmarked" + ) + + Spacer(modifier = Modifier.width(2.dp)) + } + + Text( + text = session.session.startsAt.time.toString(), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Text( + text = session.formattedDuration, + ) + } + } + } + + item { + Text( + modifier = Modifier + .padding(horizontal = 8.dp) + .padding(top = 12.dp), + text = session.session.title, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.title2, + ) + } + + if (session.speakers.isNotEmpty()) { + item { + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 16.dp), + text = session.speakers.joinToString { it.getFullNameAndCompany() }, + color = MaterialTheme.colors.primary, + textAlign = TextAlign.Center, + ) + } + } + + item { + Text( + text = session.room.name, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + ) + } + + session.session.description?.let { description -> + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(top = 16.dp), + text = description, + style = MaterialTheme.typography.body1.copy( + hyphens = Hyphens.Auto, + lineBreak = LineBreak.Paragraph, + ), + ) + } + } + + session.speakers + .filter { it.bio != null } + .forEach { speaker -> + item { + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 16.dp), + text = speaker.getFullNameAndCompany(), + color = MaterialTheme.colors.primary, + textAlign = TextAlign.Center, + ) + } + + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + text = speaker.bio!!, + style = MaterialTheme.typography.body2.copy( + hyphens = Hyphens.Auto, + lineBreak = LineBreak.Paragraph, + color = MaterialTheme.colors.onSurfaceVariant, + ), + ) + } + } + } + } +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +private fun SessionDetailsScreenPreview() { + Session( + session = uiSession1 + ) +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailViewModel.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailViewModel.kt new file mode 100644 index 00000000..c8b05f41 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/details/SessionDetailViewModel.kt @@ -0,0 +1,72 @@ +package fr.paug.androidmakers.wear.ui.session.details + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import fr.androidmakers.domain.interactor.SetSessionBookmarkUseCase +import fr.androidmakers.domain.model.Room +import fr.androidmakers.domain.model.Session +import fr.androidmakers.domain.model.Speaker +import fr.androidmakers.domain.repo.BookmarksRepository +import fr.androidmakers.domain.repo.RoomsRepository +import fr.androidmakers.domain.repo.SessionsRepository +import fr.androidmakers.domain.repo.SpeakersRepository +import fr.paug.androidmakers.wear.ui.session.UISession +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class SessionDetailViewModel( + sessionId: String, + sessionsRepository: SessionsRepository, + private val roomsRepository: RoomsRepository, + bookmarksRepository: BookmarksRepository, + private val speakersRepository: SpeakersRepository, + private val setSessionBookmarkUseCase: SetSessionBookmarkUseCase, +) : ViewModel() { + + private val session: Flow = sessionsRepository.getSession(sessionId) + .filterSuccess() + .stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = null) + .filterNotNull() + + private val room: Flow = session.map { session -> + roomsRepository.getRoom(session.roomId) + .filterSuccess() + .first() + } + + private val speakers: Flow> = session.map { session -> + session.speakers.map { + speakersRepository.getSpeaker(it).filterSuccess().first() + } + } + + private val isBookmarked: Flow = bookmarksRepository.isBookmarked(sessionId) + + val uiSession: Flow = combine( + session, + room, + speakers, + isBookmarked, + ) { session, room, speakers, isBookmarked -> + UISession( + session = session, + speakers = speakers, + room = room, + isBookmarked = isBookmarked, + ) + } + + fun bookmark(bookmarked: Boolean) = viewModelScope.launch { + setSessionBookmarkUseCase(session.first().id, bookmarked) + } +} + +private fun Flow>.filterSuccess(): Flow = + filter { it.isSuccess }.map { it.getOrThrow() } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt index 830f1810..1b1e85b0 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt @@ -2,7 +2,6 @@ package fr.paug.androidmakers.wear.ui.session.list -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,10 +17,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.Hyphens +import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.items -import androidx.wear.compose.material.CircularProgressIndicator import androidx.wear.compose.material.Icon import androidx.wear.compose.material.LocalContentColor import androidx.wear.compose.material.LocalTextStyle @@ -37,19 +37,17 @@ import com.google.android.horologist.compose.layout.ScreenScaffold import com.google.android.horologist.compose.layout.rememberResponsiveColumnState import com.google.android.horologist.compose.material.ListHeaderDefaults import com.google.android.horologist.compose.material.ResponsiveListHeader -import fr.androidmakers.domain.model.Room -import fr.androidmakers.domain.model.Session -import fr.androidmakers.domain.model.Speaker import fr.paug.androidmakers.wear.R -import fr.paug.androidmakers.wear.ui.main.UISession +import fr.paug.androidmakers.wear.ui.common.Loading +import fr.paug.androidmakers.wear.ui.session.UISession +import fr.paug.androidmakers.wear.ui.session.uiSessions import fr.paug.androidmakers.wear.ui.theme.amRed -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.Month @Composable fun SessionListScreen( sessions: List?, title: String, + onSessionClick: (String) -> Unit, ) { if (sessions == null) { Loading() @@ -57,6 +55,7 @@ fun SessionListScreen( SessionList( sessions = sessions, title = title, + onSessionClick = onSessionClick, ) } } @@ -65,6 +64,7 @@ fun SessionListScreen( private fun SessionList( sessions: List, title: String, + onSessionClick: (String) -> Unit, ) { val columnState = rememberResponsiveColumnState( contentPadding = ScalingLazyColumnDefaults.padding( @@ -93,7 +93,10 @@ private fun SessionList( } } else { items(sessions, key = { it.session.id }) { session -> - SessionItem(session) + SessionItem( + session = session, + onSessionClick = onSessionClick, + ) } } } @@ -101,7 +104,10 @@ private fun SessionList( } @Composable -private fun SessionItem(session: UISession) { +private fun SessionItem( + session: UISession, + onSessionClick: (String) -> Unit, +) { TitleCard( modifier = Modifier.fillMaxWidth(), title = { @@ -138,10 +144,17 @@ private fun SessionItem(session: UISession) { } } Spacer(modifier = Modifier.height(4.dp)) - Text(text = session.session.title) + Text( + text = session.session.title, + style = MaterialTheme.typography.title3.copy( + hyphens = Hyphens.Auto, + lineBreak = LineBreak.Paragraph, + ), + ) } }, - onClick = { /*TODO*/ }, + onClick = { onSessionClick(session.session.id) }, + enabled = !session.session.isServiceSession, ) { if (!session.session.isServiceSession) { if (session.speakers.isNotEmpty()) { @@ -160,84 +173,16 @@ private fun SessionItem(session: UISession) { } } -@Composable -private fun Loading() { - Box( - modifier = Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } -} - @WearPreviewDevices @WearPreviewFontScales @Composable private fun LoadingSessionListScreenPreview() { - SessionListScreen(null, stringResource(id = R.string.main_day1)) + SessionListScreen(null, stringResource(id = R.string.main_day1), {}) } @WearPreviewDevices @WearPreviewFontScales @Composable private fun LoadedSessionListScreenPreview() { - SessionListScreen( - listOf( - UISession( - session = Session( - id = "1", - title = "Android Graphics: the Path to [UI] Riches", - description = "Android's graphics APIs are extensive and powerful... but maybe a little complicated. This session will show ways to use the graphics APIs to achieve cool effects and improve the visual quality and richness of your applications.", - roomId = "", - speakers = emptyList(), - startsAt = LocalDateTime(2023, Month.APRIL, 27, 9, 15), - endsAt = LocalDateTime(2023, Month.APRIL, 27, 10, 0), - isServiceSession = false, - ), - speakers = listOf( - Speaker( - id = "1", - name = "Speaker 1", - bio = "Bio 1", - ), - Speaker( - id = "2", - name = "Speaker 2", - bio = "Bio 2", - ) - ), - room = Room( - id = "1", - name = "Room 1" - ), - isBookmarked = true, - ), - UISession( - session = Session( - id = "2", - title = "Using Compose Runtime to create a client library", - description = "Jetpack Compose (UI) is a powerful UI toolkit for Android. Have you ever wondered where this power comes from? The answer is Compose Runtime. \r\n\r\nIn this talk, we will see how we can use Compose Runtime to create client libraries. Firstly, we will talk about Compose nodes, Composition, Recomposer, and how they are orchestrated to create a slot table. Then, we will see how the changes in the slot table are applied with an Applier. Moreover, we will touch upon the Snapshot system and how the changes in the state objects trigger a recomposition. Finally, we will create a basic UI toolkit for PowerPoint using Compose Runtime.", - roomId = "", - speakers = emptyList(), - startsAt = LocalDateTime(2023, Month.APRIL, 27, 10, 15), - endsAt = LocalDateTime(2023, Month.APRIL, 27, 11, 0), - isServiceSession = false, - ), - speakers = listOf( - Speaker( - id = "3", - name = "Speaker 3", - bio = "Bio 3", - ), - ), - room = Room( - id = "2", - name = "Room 2" - ), - isBookmarked = false, - ), - ), - stringResource(id = R.string.main_day1) - ) + SessionListScreen(uiSessions, stringResource(id = R.string.main_day1), {}) } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/SignInScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/SignInScreen.kt index 8c74ce85..aab37f2a 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/SignInScreen.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/SignInScreen.kt @@ -21,6 +21,7 @@ fun SignInScreen( modifier = Modifier.fillMaxSize(), onAuthCancelled = { Log.d(TAG, "onAuthCancelled") + onDismissOrTimeout() }, failedContent = { AuthErrorScreen()