diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc62c30b..0538f8a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ androidx-lifecycle = "2.10.0" # Used by the Wear OS app only androidx-wear-compose = "1.6.0" apollo = "4.4.2" apollo-adapters = "0.7.0" -apollo-cache = "1.0.0" +apollo-cache = "1.0.1" coil = "3.4.0" compose = "1.10.6" # Used by the Wear OS app only compose-material-icons-extended = "1.7.8" @@ -28,7 +28,7 @@ jetbrains-compose = "1.11.0-alpha04" jetbrains-lifecycle = "2.10.0" jetbrains-material3-adaptive-nav3 = "1.3.0-alpha06" jetbrains-compose-material-icons-extended = "1.7.3" -nav3-ui = "1.1.0-alpha04" +nav3-ui = "1.1.0-beta01" junit = "4.13.2" koin = "4.1.1" kotlin = "2.3.20" diff --git a/iosApp/Android Makers.xcodeproj/project.pbxproj b/iosApp/Android Makers.xcodeproj/project.pbxproj index 2bf94261..973c2174 100644 --- a/iosApp/Android Makers.xcodeproj/project.pbxproj +++ b/iosApp/Android Makers.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 61C8A7672D8B199B0046C2CC /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 61C8A7662D8B199B0046C2CC /* FirebaseCore */; }; 61C8A7692D8B199B0046C2CC /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 61C8A7682D8B199B0046C2CC /* FirebaseFirestore */; }; 61C8A76B2D8B1C490046C2CC /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 61C8A76A2D8B1C490046C2CC /* FirebaseAuth */; }; + 61C8A76D2D8B1C490046C2CC /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 61C8A76C2D8B1C490046C2CC /* FirebaseMessaging */; }; B2CD272F234D06530016AA02 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CD272E234D06530016AA02 /* AppDelegate.swift */; }; B2CD2731234D06530016AA02 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CD2730234D06530016AA02 /* SceneDelegate.swift */; }; B2CD2733234D06530016AA02 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CD2732234D06530016AA02 /* ContentView.swift */; }; @@ -40,6 +41,7 @@ 61C8A7672D8B199B0046C2CC /* FirebaseCore in Frameworks */, 61C8A76B2D8B1C490046C2CC /* FirebaseAuth in Frameworks */, 61C8A7692D8B199B0046C2CC /* FirebaseFirestore in Frameworks */, + 61C8A76D2D8B1C490046C2CC /* FirebaseMessaging in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -131,6 +133,7 @@ 61C8A7662D8B199B0046C2CC /* FirebaseCore */, 61C8A7682D8B199B0046C2CC /* FirebaseFirestore */, 61C8A76A2D8B1C490046C2CC /* FirebaseAuth */, + 61C8A76C2D8B1C490046C2CC /* FirebaseMessaging */, ); productName = RobotConf; productReference = B2CD272B234D06530016AA02 /* Android Makers.app */; @@ -205,7 +208,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode"; }; /* End PBXShellScriptBuildPhase section */ @@ -480,6 +483,11 @@ package = 61C8A7632D8B199B0046C2CC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAuth; }; + 61C8A76C2D8B1C490046C2CC /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 61C8A7632D8B199B0046C2CC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B2CD2723234D06530016AA02 /* Project object */; diff --git a/shared/ui/src/commonMain/composeResources/values/strings.xml b/shared/ui/src/commonMain/composeResources/values/strings.xml index aa018c11..fc875354 100644 --- a/shared/ui/src/commonMain/composeResources/values/strings.xml +++ b/shared/ui/src/commonMain/composeResources/values/strings.xml @@ -49,7 +49,7 @@ Expert - Android Makers by droidcon is a two days event held in Paris on April 9th and 10th 2025. Join us in tackling the present and future of Android with the hottest experts of the domain. There will be technical sessions, workshops and opportunities to meet your favorites speakers. All the Talks will be recorded, and uploaded on the Youtube channel. + Android Makers by droidcon is a two days event held in Paris on April 9th and 10th 2026. Join us in tackling the present and future of Android with the hottest experts of the domain. There will be technical sessions, workshops and opportunities to meet your favorites speakers. All the Talks will be recorded, and uploaded on the Youtube channel. Open the slides diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/MainLayout.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/MainLayout.kt index 7b315a0c..2f77b3f3 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/MainLayout.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/MainLayout.kt @@ -100,7 +100,7 @@ fun MainLayout( ) } - } // AndroidMakersTheme + } } @Composable diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt index e8f230e3..f3ca4fa1 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt @@ -1,11 +1,18 @@ package com.androidmakers.ui.common.navigation +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api @@ -16,6 +23,7 @@ import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -297,7 +305,13 @@ private fun AVANavDisplay( val entryProvider = entryProvider { // Tab entries entry { - FeedScreen() + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + FeedScreen() + } } entry( @@ -305,32 +319,56 @@ private fun AVANavDisplay( detailPlaceholder = {} ) ) { - AgendaLayout( - showFilterBottomSheet = showAgendaFilterBottomSheet, - onFilterBottomSheetDismiss = onDismissAgendaFilter, - onSessionClick = { sessionId -> navigator.navigateToSessionDetail(sessionId) } - ) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + AgendaLayout( + showFilterBottomSheet = showAgendaFilterBottomSheet, + onFilterBottomSheetDismiss = onDismissAgendaFilter, + onSessionClick = { sessionId -> navigator.navigateToSessionDetail(sessionId) } + ) + } } entry { - SpeakerScreen( - viewModel = koinViewModel(), - navigateToSpeakerDetails = { speakerId -> navigator.navigate(SpeakerDetailKey(speakerId)) }, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + SpeakerScreen( + viewModel = koinViewModel(), + navigateToSpeakerDetails = { speakerId -> navigator.navigate(SpeakerDetailKey(speakerId)) }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + } } entry { - SponsorsScreen() + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + SponsorsScreen() + } } entry { - InfoScreen( - versionCode = versionCode, - versionName = versionName, - featureFlags = featureFlags, - ) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + InfoScreen( + versionCode = versionCode, + versionName = versionName, + featureFlags = featureFlags, + ) + } } // Detail entries @@ -341,41 +379,61 @@ private fun AVANavDisplay( BottomSheetSceneStrategy.bottomSheet() } ) { key -> - SessionDetailScreen( - viewModel = koinViewModel(key = key.sessionId) { parametersOf(key.sessionId) }, - onBackClick = { navigator.goBack() }, - onSpeakerClick = { speakerId -> navigator.navigate(SpeakerDetailKey(speakerId)) }, - showBackButton = isWideScreen, - showTopBar = isWideScreen, - sharedTransitionScope = sharedTransitionScope, - // LocalNavAnimatedContentScope is unavailable in OverlayScene (bottom sheet) - animatedVisibilityScope = if (isWideScreen) { - LocalNavAnimatedContentScope.current - } else { - null - }, - ) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + SessionDetailScreen( + viewModel = koinViewModel(key = key.sessionId) { parametersOf(key.sessionId) }, + onBackClick = { navigator.goBack() }, + onSpeakerClick = { speakerId -> navigator.navigate(SpeakerDetailKey(speakerId)) }, + showBackButton = isWideScreen, + showTopBar = isWideScreen, + sharedTransitionScope = sharedTransitionScope, + // LocalNavAnimatedContentScope is unavailable in OverlayScene (bottom sheet) + animatedVisibilityScope = if (isWideScreen) { + LocalNavAnimatedContentScope.current + } else { + null + }, + ) + } } entry { key -> - SpeakerDetailsRoute( - speakerDetailsViewModel = koinViewModel(key = key.speakerId) { parametersOf(key.speakerId) }, - onBackClick = { navigator.goBack() }, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ) { + SpeakerDetailsRoute( + speakerDetailsViewModel = koinViewModel(key = key.speakerId) { parametersOf(key.speakerId) }, + onBackClick = { navigator.goBack() }, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + } } } val bottomSheetStrategy = remember { BottomSheetSceneStrategy() } val listDetailStrategy = rememberListDetailSceneStrategy() + NavDisplay( entries = navigationState.toDecoratedEntries(entryProvider), sceneStrategies = listOf(bottomSheetStrategy, listDetailStrategy), - onBack = { navigator.goBack() } + onBack = navigator::goBack, + transitionSpec = { + fadeIn(tween(200)) togetherWith ExitTransition.None + }, + popTransitionSpec = { + EnterTransition.None togetherWith fadeOut(tween(200)) + }, ) } + } @Composable diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/feed/FeedScreen.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/feed/FeedScreen.kt index b63cc289..c12f110a 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/feed/FeedScreen.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/feed/FeedScreen.kt @@ -18,7 +18,10 @@ fun FeedScreen() { val lce by viewModel.values.collectAsStateWithLifecycle() val dismissedAlertIds by viewModel.dismissedAlertIds.collectAsStateWithLifecycle() - LceLayout(lce = lce, onRetry = { viewModel.refresh() }) { feedItems -> + LceLayout( + lce = lce, + onRetry = { viewModel.refresh() } + ) { feedItems -> val visibleItems = feedItems.filter { item -> item !is FeedItem.Alert || item.id !in dismissedAlertIds } diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerListScreen.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerListScreen.kt index 843c76b6..14351c58 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerListScreen.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerListScreen.kt @@ -50,10 +50,10 @@ import com.androidmakers.ui.theme.LocalIsNeobrutalism import com.androidmakers.ui.theme.neoBrutalElevation import fr.androidmakers.domain.model.Speaker import fr.paug.androidmakers.ui.Res +import fr.paug.androidmakers.ui.back import fr.paug.androidmakers.ui.ic_arrow_back import fr.paug.androidmakers.ui.ic_clear import fr.paug.androidmakers.ui.ic_search -import fr.paug.androidmakers.ui.back import fr.paug.androidmakers.ui.speaker_search_placeholder import fr.paug.androidmakers.ui.speakers import org.jetbrains.compose.resources.painterResource @@ -62,10 +62,10 @@ import org.jetbrains.compose.resources.stringResource @OptIn(ExperimentalMaterial3Api::class) @Composable fun SpeakerScreen( - viewModel: SpeakerListViewModel, - navigateToSpeakerDetails: (String) -> Unit, - sharedTransitionScope: SharedTransitionScope? = null, - animatedVisibilityScope: AnimatedVisibilityScope? = null, + viewModel: SpeakerListViewModel, + navigateToSpeakerDetails: (String) -> Unit, + sharedTransitionScope: SharedTransitionScope? = null, + animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { val state by viewModel.uiState.collectAsStateWithLifecycle() @@ -81,7 +81,7 @@ fun SpeakerScreen( val speakers = (state as Lce.Content).content.speakers val filteredSpeakers = remember(speakers, text) { - speakers.filter { it.name?.contains(text, ignoreCase = true) == true } + speakers.filter { it.name?.contains(text, ignoreCase = true) == true } } var searchHeight by remember { mutableStateOf(56.dp) } @@ -112,7 +112,6 @@ fun SpeakerScreen( } } - } @Suppress("LongParameterList") @@ -232,11 +231,11 @@ private fun SpeakerListContent( @Composable fun SpeakerItem( - speaker: Speaker, - modifier: Modifier = Modifier, - navigateToSpeakerDetails: (String) -> Unit, - sharedTransitionScope: SharedTransitionScope? = null, - animatedVisibilityScope: AnimatedVisibilityScope? = null, + speaker: Speaker, + modifier: Modifier = Modifier, + navigateToSpeakerDetails: (String) -> Unit, + sharedTransitionScope: SharedTransitionScope? = null, + animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { val circularShape = if (LocalIsNeobrutalism.current) RectangleShape else CircleShape @@ -245,13 +244,13 @@ fun SpeakerItem( .fillMaxWidth() .neoBrutalElevation() .clickable(onClick = { navigateToSpeakerDetails(speaker.id) }), - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { Row( - modifier = Modifier.padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, ) { speaker.photoUrl?.let { url -> val photoModifier = if (sharedTransitionScope != null && animatedVisibilityScope != null) { @@ -265,23 +264,23 @@ fun SpeakerItem( Modifier } AsyncImage( - model = url, - modifier = photoModifier - .size(56.dp) - .clip(circularShape), - contentDescription = stringResource(Res.string.speakers) + model = url, + modifier = photoModifier + .size(56.dp) + .clip(circularShape), + contentDescription = stringResource(Res.string.speakers) ) } Column { Text( - text = speaker.name.orEmpty(), - style = MaterialTheme.typography.titleMedium, + text = speaker.name.orEmpty(), + style = MaterialTheme.typography.titleMedium, ) speaker.company?.let { company -> Text( - text = company, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + text = company, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } }