diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9317c2eb..f5ad72f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,8 @@ pullrefresh = "1.3.0" androidx-wear-compose = "1.3.0" playServicesWearable = "18.1.0" compose-ui-tooling = "1.3.0" -horologist = "0.5.24" +horologist = "0.6.5" +kprefs = "1.7.2" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } @@ -45,6 +46,7 @@ compose-material = { group = "androidx.compose.material", name = "material", ver compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } espresso-core = "androidx.test.espresso:espresso-core:3.5.1" firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx" } +firebase-auth-ktx = { module = "com.google.firebase:firebase-auth-ktx" } firebase-bom = "com.google.firebase:firebase-bom:32.7.2" firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx" } firebase-inappmessaging = { module = "com.google.firebase:firebase-inappmessaging-display-ktx" } @@ -91,14 +93,16 @@ play-services-wearable = { group = "com.google.android.gms", name = "play-servic androidx-splashscreen = "androidx.core:core-splashscreen:1.0.1" wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "androidx-wear-compose" } wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "androidx-wear-compose" } -androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "compose" } +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } horologist-composables = { module = "com.google.android.horologist:horologist-composables", version.ref = "horologist" } horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" } horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" } +horologist-auth-ui = { module = "com.google.android.horologist:horologist-auth-ui", version.ref = "horologist" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } androidx-compose-ui-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "compose-ui-tooling" } wear-compose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "androidx-wear-compose" } desugar-jdk-libs = "com.android.tools:desugar_jdk_libs:2.0.4" +kprefs = { module = "org.jraf:kprefs", version.ref = "kprefs" } [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } diff --git a/shared/data/src/androidMain/kotlin/fr/androidmakers/store/graphql/ApolloClientBuilder.android.kt b/shared/data/src/androidMain/kotlin/fr/androidmakers/store/graphql/ApolloClientBuilder.android.kt index 985a5834..111b8a78 100644 --- a/shared/data/src/androidMain/kotlin/fr/androidmakers/store/graphql/ApolloClientBuilder.android.kt +++ b/shared/data/src/androidMain/kotlin/fr/androidmakers/store/graphql/ApolloClientBuilder.android.kt @@ -1,7 +1,6 @@ package fr.androidmakers.store.graphql import android.content.Context -import android.os.Build import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.http.HttpRequest import com.apollographql.apollo3.api.http.HttpResponse @@ -12,7 +11,6 @@ import com.apollographql.apollo3.network.http.HttpInterceptor import com.apollographql.apollo3.network.http.HttpInterceptorChain import com.google.firebase.auth.ktx.auth import com.google.firebase.ktx.Firebase -import kotlinx.coroutines.runBlocking actual class ApolloClientBuilder( context: Context, @@ -29,9 +27,7 @@ actual class ApolloClientBuilder( request.newBuilder() .addHeader("conference", conference) .apply { - val token = runBlocking { - Firebase.auth.currentUser?.getIdToken(false)?.result?.token - } + val token = Firebase.auth.currentUser?.getIdToken(false)?.result?.token if (token != null) { addHeader("Authorization", "Bearer $token") } diff --git a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt index 46955750..418d987b 100644 --- a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt +++ b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt @@ -6,7 +6,7 @@ import fr.androidmakers.domain.model.User import fr.androidmakers.domain.repo.UserRepository class FirebaseUserRepository : UserRepository { - override suspend fun getUser(): User? { + override fun getUser(): User? { return try { Firebase.auth.currentUser?.toUser() } catch (e: Exception) { diff --git a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/graphql/mappers.kt b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/graphql/mappers.kt index f444ddb3..a705405c 100644 --- a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/graphql/mappers.kt +++ b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/graphql/mappers.kt @@ -1,14 +1,14 @@ package fr.androidmakers.store.graphql -import fr.androidmakers.store.graphql.fragment.RoomDetails -import fr.androidmakers.store.graphql.fragment.SessionDetails -import fr.androidmakers.store.graphql.fragment.SpeakerDetails import fr.androidmakers.domain.model.Complexity import fr.androidmakers.domain.model.Room import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.SocialsItem import fr.androidmakers.domain.model.Speaker import fr.androidmakers.domain.model.Venue +import fr.androidmakers.store.graphql.fragment.RoomDetails +import fr.androidmakers.store.graphql.fragment.SessionDetails +import fr.androidmakers.store.graphql.fragment.SpeakerDetails fun SpeakerDetails.toSpeaker(): Speaker { return Speaker( diff --git a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/model/Session.kt b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/model/Session.kt index d1005d0f..1009905b 100644 --- a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/model/Session.kt +++ b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/model/Session.kt @@ -1,6 +1,8 @@ package fr.androidmakers.domain.model import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant import kotlin.time.Duration data class Session( @@ -16,11 +18,11 @@ data class Session( val platformUrl: String? = null, //TODO unsure val slidesUrl: String? = null, - val duration: Duration = Duration.ZERO, // TODO move to instant to handle timezone val startsAt: LocalDateTime, val endsAt: LocalDateTime, + val duration: Duration = endsAt.toInstant(TimeZone.UTC) - startsAt.toInstant(TimeZone.UTC), val roomId: String, val isServiceSession: Boolean, ) diff --git a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/repo/UserRepository.kt b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/repo/UserRepository.kt index 7d034e85..92b91e84 100644 --- a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/repo/UserRepository.kt +++ b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/repo/UserRepository.kt @@ -3,5 +3,5 @@ package fr.androidmakers.domain.repo import fr.androidmakers.domain.model.User interface UserRepository { - suspend fun getUser(): User? + fun getUser(): User? } diff --git a/wearApp/build.gradle.kts b/wearApp/build.gradle.kts index cff00ad9..a79ec65d 100644 --- a/wearApp/build.gradle.kts +++ b/wearApp/build.gradle.kts @@ -2,6 +2,8 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.google.services) + + alias(libs.plugins.androidmakers.android.signing) } android { @@ -9,6 +11,7 @@ android { compileSdk = 34 defaultConfig { + applicationId = "fr.paug.androidmakers" minSdk = 30 targetSdk = 34 versionCode = 1 @@ -18,13 +21,6 @@ android { } } - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -52,21 +48,27 @@ android { dependencies { implementation(libs.play.services.wearable) + implementation(libs.play.services.auth) implementation(libs.androidx.activity.compose) implementation(libs.androidx.splashscreen) implementation(libs.wear.compose.material) implementation(libs.wear.compose.foundation) - implementation(libs.androidx.material.icons.core) + implementation(libs.androidx.material.icons.extended) implementation(libs.horologist.composables) implementation(libs.horologist.compose.layout) implementation(libs.horologist.compose.material) + implementation(libs.horologist.auth.ui) implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling) implementation(libs.wear.compose.navigation) + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.auth.ktx) + implementation(libs.kprefs) coreLibraryDesugaring(libs.desugar.jdk.libs) debugImplementation(libs.compose.ui.tooling) implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) implementation(project(":shared:di")) implementation(project(":shared:domain")) } diff --git a/wearApp/google-services.json b/wearApp/google-services.json deleted file mode 100644 index 5eb612f0..00000000 --- a/wearApp/google-services.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "project_info": { - "project_number": "127852231544", - "project_id": "androidmakers-2023", - "storage_bucket": "androidmakers-2023.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:127852231544:android:36ae3551145453a9e6e68d", - "android_client_info": { - "package_name": "fr.paug.androidmakers.wear" - } - }, - "oauth_client": [ - { - "client_id": "127852231544-bmorteqbdjjjffq7uirf6i8db0t4bo71.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "fr.paug.androidmakers.wear", - "certificate_hash": "a1eb323c4b06a569e8db487bb03a65d2ea649f48" - } - }, - { - "client_id": "127852231544-nislnj6eo41sbj5f9mmbeedcqdrk1pib.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "fr.paug.androidmakers.wear", - "certificate_hash": "ff357f347e107c052e57095034000d9a312275a5" - } - }, - { - "client_id": "127852231544-1nj0bjn8v2h7psd1lnf8ppovoe3k245b.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD0MDezelAjCX4IaK2Me-NSX0GSKFmMxHc" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "127852231544-119kptis2pedq9rav7s8jto1mlnp208o.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} diff --git a/wearApp/google-services.json b/wearApp/google-services.json new file mode 120000 index 00000000..a5424d48 --- /dev/null +++ b/wearApp/google-services.json @@ -0,0 +1 @@ +../androidApp/google-services.json \ No newline at end of file diff --git a/wearApp/keystore.debug b/wearApp/keystore.debug new file mode 120000 index 00000000..50ad1aca --- /dev/null +++ b/wearApp/keystore.debug @@ -0,0 +1 @@ +../androidApp/keystore.debug \ No newline at end of file diff --git a/wearApp/keystore.release b/wearApp/keystore.release new file mode 120000 index 00000000..925ee114 --- /dev/null +++ b/wearApp/keystore.release @@ -0,0 +1 @@ +../androidApp/keystore.release \ No newline at end of file diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/AndroidMakersWearApplication.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/AndroidMakersWearApplication.kt index ac128e1a..6ad02ca8 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/AndroidMakersWearApplication.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/AndroidMakersWearApplication.kt @@ -1,14 +1,22 @@ package fr.paug.androidmakers.wear; import android.app.Application +import android.content.Context import fr.androidmakers.di.DependenciesBuilder import fr.paug.androidmakers.wear.di.androidViewModelModule +import fr.paug.androidmakers.wear.di.dataModule + +lateinit var applicationContext: Context class AndroidMakersWearApplication : Application() { override fun onCreate() { super.onCreate() + fr.paug.androidmakers.wear.applicationContext = applicationContext DependenciesBuilder(this).inject( - listOf(androidViewModelModule) + listOf( + androidViewModelModule, + dataModule, + ) ) } } diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/data/LocalPreferencesRepository.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/data/LocalPreferencesRepository.kt new file mode 100644 index 00000000..9b0b21ab --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/data/LocalPreferencesRepository.kt @@ -0,0 +1,11 @@ +package fr.paug.androidmakers.wear.data + +import android.content.Context +import kotlinx.coroutines.flow.MutableStateFlow +import org.jraf.android.kprefs.Prefs + +class LocalPreferencesRepository(applicationContext: Context) { + private val localPrefs = Prefs(applicationContext) + + val showOnlyBookmarkedSessions: MutableStateFlow by localPrefs.BooleanFlow(false) +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/di/DataModule.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/di/DataModule.kt new file mode 100644 index 00000000..82133329 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/di/DataModule.kt @@ -0,0 +1,8 @@ +package fr.paug.androidmakers.wear.di + +import fr.paug.androidmakers.wear.data.LocalPreferencesRepository +import org.koin.dsl.module + +val dataModule = module { + single { LocalPreferencesRepository(get()) } +} 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 a0d8d799..b7fb145c 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,9 +1,11 @@ package fr.paug.androidmakers.wear.di import fr.paug.androidmakers.wear.ui.main.MainViewModel +import fr.paug.androidmakers.wear.ui.settings.SettingsViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val androidViewModelModule = module { - viewModel { MainViewModel(get(), get()) } + viewModel { MainViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { SettingsViewModel(get(), get()) } } 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 5082043a..67e5dea9 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 @@ -1,241 +1,108 @@ -@file:OptIn(ExperimentalHorologistApi::class, ExperimentalWearFoundationApi::class) - package fr.paug.androidmakers.wear.ui.main import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.ScrollState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Build +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +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.compose.ui.text.style.TextAlign import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.wear.compose.foundation.ExperimentalWearFoundationApi -import androidx.wear.compose.material.MaterialTheme -import androidx.wear.compose.material.Text -import androidx.wear.compose.material.TitleCard -import androidx.wear.compose.material.dialog.Dialog import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController -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.AppScaffold -import com.google.android.horologist.compose.layout.ScalingLazyColumn -import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults -import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType import com.google.android.horologist.compose.layout.ScreenScaffold -import com.google.android.horologist.compose.layout.rememberResponsiveColumnState -import com.google.android.horologist.compose.material.AlertContent -import com.google.android.horologist.compose.material.Button -import com.google.android.horologist.compose.material.Chip -import com.google.android.horologist.compose.material.ListHeaderDefaults.firstItemPadding -import com.google.android.horologist.compose.material.ResponsiveListHeader -import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll +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.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.viewmodel.ext.android.viewModel +import org.koin.androidx.compose.koinViewModel class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - val viewModel: MainViewModel by viewModel() - viewModel.logAgenda() - setContent { installSplashScreen() - WearApp() - setTheme(android.R.style.Theme_DeviceDefault) } } } @Composable -fun WearApp() { +fun WearApp( + viewModel: MainViewModel = koinViewModel(), +) { val navController = rememberSwipeDismissableNavController() - + val onSignInClick: () -> Unit = { + navController.navigate(Navigation.SIGN_IN) + } + val onSignInDismissOrTimeout: () -> Unit = { + navController.popBackStack() + } + val onSignInSuccess = viewModel::onSignInSuccess AndroidMakersWearTheme { AppScaffold { - SwipeDismissableNavHost(navController = navController, startDestination = "menu") { - composable("menu") { - GreetingScreen( - "Android", - onShowList = { navController.navigate("list") } + SwipeDismissableNavHost(navController = navController, startDestination = Navigation.MAIN) { + composable(Navigation.MAIN) { + MainScreen( + onSignInClick = onSignInClick, + onSignOutClick = { viewModel.signOut() }, + viewModel = viewModel, ) } - composable("list") { - ListScreen() - } - } - } - } -} - -@Composable -fun GreetingScreen(greetingName: String, onShowList: () -> Unit) { - val scrollState = ScrollState(0) - - /* If you have enough items in your list, use [ScalingLazyColumn] which is an optimized - * version of LazyColumn for wear devices with some added features. For more information, - * see d.android.com/wear/compose. - */ - ScreenScaffold(scrollState = scrollState) { - val padding = ScalingLazyColumnDefaults.padding( - first = ItemType.Text, - last = ItemType.Chip - )() - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .rotaryWithScroll(scrollState) - .padding(padding), - verticalArrangement = Arrangement.Center - ) { - Greeting(greetingName = greetingName) - Chip(label = "Show List", onClick = onShowList) - } - } -} - -@Composable -fun ListScreen() { - var showDialog by remember { mutableStateOf(false) } - - /* - * Specifying the types of items that appear at the start and end of the list ensures that the - * appropriate padding is used. - */ - val columnState = rememberResponsiveColumnState( - contentPadding = ScalingLazyColumnDefaults.padding( - first = ItemType.Text, - last = ItemType.SingleButton - ) - ) - - ScreenScaffold(scrollState = columnState) { - /* - * The Horologist [ScalingLazyColumn] takes care of the horizontal and vertical - * padding for the list, so there is no need to specify it, as in the [GreetingScreen] - * composable. - */ - ScalingLazyColumn( - columnState = columnState, - modifier = Modifier - .fillMaxSize() - ) { - item { - ResponsiveListHeader(contentPadding = firstItemPadding()) { - Text(text = "Header") - } - } - item { - TitleCard(title = { Text("Example Title") }, onClick = { }) { - Text("Example Content\nMore Lines\nAnd More") + composable(Navigation.SIGN_IN) { + SignInScreen( + onSignInSuccess = onSignInSuccess, + onDismissOrTimeout = onSignInDismissOrTimeout + ) } } - item { - Chip(label = "Example Chip", onClick = { }) - } - item { - Button( - imageVector = Icons.Default.Build, - contentDescription = "Example Button", - onClick = { showDialog = true } - ) - } } } - - SampleDialog( - showDialog = showDialog, - onDismiss = { showDialog = false }, - onCancel = {}, - onOk = {} - ) -} - -@Composable -fun Greeting(greetingName: String) { - ResponsiveListHeader(contentPadding = firstItemPadding()) { - Text( - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - color = MaterialTheme.colors.primary, - text = stringResource(R.string.hello_world, greetingName) - ) - } } @Composable -fun SampleDialog( - showDialog: Boolean, - onDismiss: () -> Unit, - onCancel: () -> Unit, - onOk: () -> Unit +fun MainScreen( + viewModel: MainViewModel, + onSignInClick: () -> Unit, + onSignOutClick: () -> Unit, ) { - val state = rememberResponsiveColumnState() + ScreenScaffold { + val pagerState: PagerState = rememberPagerState(initialPage = viewModel.getConferenceDay() + 1, pageCount = { 3 }) + val user: User? by viewModel.user.collectAsState() + val sessionsDay1: List? by viewModel.sessionsDay1.collectAsState(initial = null) + val sessionsDay2: List? by viewModel.sessionsDay2.collectAsState(initial = null) + + PagerScreen( + modifier = Modifier.fillMaxSize(), + state = pagerState + ) { page -> + when (page) { + 0 -> { + SettingsScreen( + user = user, + onSignInClick = onSignInClick, + onSignOutInClick = onSignOutClick, + ) + } - Dialog( - showDialog = showDialog, - onDismissRequest = onDismiss, - scrollState = state.state - ) { - SampleDialogContent(onCancel, onDismiss, onOk) - } -} + 1 -> { + SessionListScreen(sessions = sessionsDay1, title = stringResource(id = R.string.main_day1)) + } -@Composable -fun SampleDialogContent( - onCancel: () -> Unit, - onDismiss: () -> Unit, - onOk: () -> Unit -) { - AlertContent( - icon = {}, - title = "Title", - onCancel = { - onCancel() - onDismiss() - }, - onOk = { - onOk() - onDismiss() + 2 -> { + SessionListScreen(sessions = sessionsDay2, title = stringResource(id = R.string.main_day2)) + } } - ) { - item { - Text(text = "An unknown error occurred during the request.") } } } - -@WearPreviewDevices -@WearPreviewFontScales -@Composable -fun GreetingScreenPreview() { - GreetingScreen("Preview Android", onShowList = {}) -} - -@WearPreviewDevices -@WearPreviewFontScales -@Composable -fun ListScreenPreview() { - ListScreen() -} 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 dc4f282f..46c64645 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 @@ -1,21 +1,140 @@ package fr.paug.androidmakers.wear.ui.main import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.firebase.auth.ktx.auth +import com.google.firebase.ktx.Firebase import fr.androidmakers.domain.interactor.GetAgendaUseCase +import fr.androidmakers.domain.interactor.GetFavoriteSessionsUseCase +import fr.androidmakers.domain.interactor.SyncBookmarksUseCase +import fr.androidmakers.domain.model.Agenda +import fr.androidmakers.domain.model.User +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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn +import java.time.Month + +private val TAG = MainViewModel::class.java.simpleName + +// TODO Update this date with 2024 edition date! +private val DAY_1_DATE = LocalDate(year = 2023, month = Month.APRIL, dayOfMonth = 27) +private val DAY_2_DATE = DAY_1_DATE.plus(1, DateTimeUnit.DAY) class MainViewModel( application: Application, - private val getAgendaUseCase: GetAgendaUseCase, + private val userRepository: UserRepository, + localPreferencesRepository: LocalPreferencesRepository, + getAgendaUseCase: GetAgendaUseCase, + private val syncBookmarksUseCase: SyncBookmarksUseCase, + getFavoriteSessionsUseCase: GetFavoriteSessionsUseCase, ) : AndroidViewModel(application) { + private val _user = MutableStateFlow(null) + + val user: StateFlow = _user - fun logAgenda() { + init { viewModelScope.launch { - getAgendaUseCase().collect { - println(it.getOrNull()?.sessions) + _user.emit(userRepository.getUser()) + maybeSyncBookmarks() + } + } + + private suspend fun maybeSyncBookmarks() { + val currentUser = _user.value + if (currentUser != null) { + Log.d(TAG, "Syncing bookmarks") + syncBookmarksUseCase(currentUser.id) + Log.d(TAG, "Bookmarks synced") + } + } + + private val sessions: Flow?> = getAgendaUseCase() + .filter { it.isSuccess } + .map { it.getOrThrow() } + .combine(getFavoriteSessionsUseCase()) { agenda, favoriteSessions -> + agenda.toUISessions(favoriteSessions) } + .combine(localPreferencesRepository.showOnlyBookmarkedSessions) { sessions, showOnlyBookmarked -> + if (showOnlyBookmarked) { + sessions.filter { it.isBookmarked } + } else { + sessions + } + } + .stateIn(viewModelScope, SharingStarted.Lazily, null) + + val sessionsDay1 = sessions.map { sessions -> sessions?.filter { it.session.startsAt.date == DAY_1_DATE } } + + val sessionsDay2 = sessions.map { sessions -> sessions?.filter { it.session.startsAt.date == DAY_2_DATE } } + + /** + * Get the day of the conference, based on the current date. + * If the date is the first day of the conference or earlier, returns 0, otherwise returns 1. + */ + fun getConferenceDay(): Int { + return if (Clock.System.todayIn(TimeZone.currentSystemDefault()) <= DAY_1_DATE) { + 0 + } else { + 1 + } + } + + fun onSignInSuccess() { + _user.value = userRepository.getUser() + viewModelScope.launch { + maybeSyncBookmarks() } } + + fun signOut() { + val googleSignInClient = GoogleSignIn.getClient( + applicationContext, + GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(applicationContext.getString(R.string.default_web_client_id)) + .build() + ) + googleSignInClient.signOut() + googleSignInClient.revokeAccess() + Firebase.auth.signOut() + _user.value = null + } +} + +private fun Agenda.toUISessions(favoriteSessions: Set): List { + return sessions.values.mapNotNull { session -> + UISession( + session = session, + speakers = session.speakers.map { + speakers[it] ?: run { + Log.d(TAG, "Speaker $it not found") + return@mapNotNull null + } + }, + room = rooms[session.roomId] ?: run { + Log.d(TAG, "Room ${session.roomId} not found") + return@mapNotNull null + }, + isBookmarked = session.id in favoriteSessions, + ) + } } 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 new file mode 100644 index 00000000..318a8e75 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/Navigation.kt @@ -0,0 +1,6 @@ +package fr.paug.androidmakers.wear.ui.main + +object Navigation { + const val MAIN = "MAIN" + const val SIGN_IN = "SIGN_IN" +} 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/main/UISession.kt new file mode 100644 index 00000000..b5e192c1 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/main/UISession.kt @@ -0,0 +1,14 @@ +package fr.paug.androidmakers.wear.ui.main + +import fr.androidmakers.domain.model.Room +import fr.androidmakers.domain.model.Session +import fr.androidmakers.domain.model.Speaker + +data class UISession( + val session: Session, + val speakers: List, + val room: Room, + val isBookmarked: Boolean, +) { + val formattedDuration: String = session.duration.inWholeMinutes.toString() + " min" +} 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 new file mode 100644 index 00000000..e1a65622 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/session/list/SessionListScreen.kt @@ -0,0 +1,230 @@ +@file:OptIn(ExperimentalHorologistApi::class) + +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 +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +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 +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.TitleCard +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.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 kotlinx.datetime.LocalDateTime +import kotlinx.datetime.Month + +@Composable +fun SessionListScreen( + sessions: List?, + title: String, +) { + if (sessions == null) { + Loading() + } else { + SessionList( + sessions = sessions, + title = title, + ) + } +} + +@Composable +private fun SessionList( + sessions: List, + title: String, +) { + val columnState = rememberResponsiveColumnState() + ScalingLazyColumn( + columnState = columnState, + modifier = Modifier.fillMaxSize() + ) { + item { + ResponsiveListHeader(contentPadding = ListHeaderDefaults.firstItemPadding()) { + Text(text = title) + } + } + if (sessions.isEmpty()) { + item { + Text( + text = stringResource(id = R.string.session_list_noSessions), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.caption2, + ) + } + } else { + items(sessions, key = { it.session.id }) { session -> + SessionItem(session) + } + } + } +} + +@Composable +private fun SessionItem(session: UISession) { + TitleCard( + modifier = Modifier.fillMaxWidth(), + title = { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colors.onSurfaceVariant, + LocalTextStyle provides MaterialTheme.typography.caption1, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + if (session.isBookmarked) { + Icon( + modifier = Modifier.size(18.dp), + imageVector = Icons.Rounded.Bookmark, + tint = MaterialTheme.colors.secondaryVariant, + contentDescription = "Bookmarked" + ) + } + + Text( + modifier = Modifier.weight(1F), + text = session.session.startsAt.time.toString(), + ) + + Text( + text = session.formattedDuration, + ) + } + } + Spacer(modifier = Modifier.height(4.dp)) + Text(text = session.session.title) + } + }, + onClick = { /*TODO*/ }, + ) { + if (!session.session.isServiceSession) { + if (session.speakers.isNotEmpty()) { + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = session.speakers.joinToString { it.getFullNameAndCompany() }, + color = MaterialTheme.colors.primaryVariant, + ) + Spacer(modifier = Modifier.height(4.dp)) + } + Text( + text = session.room.name, + color = MaterialTheme.colors.secondary, + ) + } + } +} + +@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)) +} + +@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) + ) +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsScreen.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsScreen.kt new file mode 100644 index 00000000..e5cbb611 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsScreen.kt @@ -0,0 +1,126 @@ +@file:OptIn(ExperimentalHorologistApi::class) + +package fr.paug.androidmakers.wear.ui.settings + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.wear.compose.material.Chip +import androidx.wear.compose.material.Switch +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.ToggleChip +import androidx.wear.compose.material.dialog.Dialog +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.rememberResponsiveColumnState +import com.google.android.horologist.compose.material.AlertContent +import fr.androidmakers.domain.model.User +import fr.paug.androidmakers.wear.R +import org.koin.androidx.compose.koinViewModel + +@Composable +fun SettingsScreen( + viewModel: SettingsViewModel = koinViewModel(), + user: User?, + onSignInClick: () -> Unit, + onSignOutInClick: () -> Unit, +) { + var showSignOutConfirmDialog by remember { mutableStateOf(false) } + val columnState = rememberResponsiveColumnState() + ScalingLazyColumn( + columnState = columnState, + modifier = Modifier.fillMaxSize() + ) { + item { + if (user == null) { + Chip( + modifier = Modifier.fillMaxWidth(), + label = { Text(stringResource(R.string.settings_signIn)) }, + onClick = onSignInClick + ) + } else { + Chip( + modifier = Modifier.fillMaxWidth(), + label = { Text(stringResource(R.string.settings_signOut)) }, + onClick = { + showSignOutConfirmDialog = true + } + ) + } + } + + item { + val showOnlyBookmarkedSessions: Boolean by viewModel.showOnlyBookmarkedSessions.collectAsStateWithLifecycle() + ToggleChip( + modifier = Modifier.fillMaxWidth(), + checked = showOnlyBookmarkedSessions, + onCheckedChange = { checked -> + viewModel.setShowOnlyBookmarkedSessions(checked) + }, + label = { Text(stringResource(R.string.settings_showBookmarksOnly)) }, + toggleControl = { + Switch( + checked = showOnlyBookmarkedSessions, + enabled = true, + ) + }, + ) + } + } + + SignOutConfirmDialog( + showDialog = showSignOutConfirmDialog, + onOk = { + onSignOutInClick() + showSignOutConfirmDialog = false + }, + onCancel = { + showSignOutConfirmDialog = false + }, + ) +} + +@Composable +private fun SignOutConfirmDialog( + showDialog: Boolean, + onOk: () -> Unit, + onCancel: () -> Unit, +) { + Dialog( + showDialog = showDialog, + onDismissRequest = onCancel, + ) { + AlertContent( + onOk = { + onOk() + }, + onCancel = { + onCancel() + }, + ) { + item { + Text(text = "Sign out?") + } + } + } +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +private fun SettingsScreenPreview() { + SettingsScreen( + user = null, + onSignInClick = {}, + onSignOutInClick = {}, + ) +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsViewModel.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsViewModel.kt new file mode 100644 index 00000000..ee2af75c --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/settings/SettingsViewModel.kt @@ -0,0 +1,17 @@ +package fr.paug.androidmakers.wear.ui.settings + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import fr.paug.androidmakers.wear.data.LocalPreferencesRepository + +class SettingsViewModel( + application: Application, + private val localPreferencesRepository: LocalPreferencesRepository, +) : AndroidViewModel(application) { + + val showOnlyBookmarkedSessions = localPreferencesRepository.showOnlyBookmarkedSessions + + fun setShowOnlyBookmarkedSessions(showOnlyBookmarkedSessions: Boolean) { + localPreferencesRepository.showOnlyBookmarkedSessions.value = showOnlyBookmarkedSessions + } +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/GoogleSignInViewModel.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/GoogleSignInViewModel.kt new file mode 100644 index 00000000..f8d9de2c --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/GoogleSignInViewModel.kt @@ -0,0 +1,43 @@ +package fr.paug.androidmakers.wear.ui.signin + +import android.util.Log +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInAccount +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.android.horologist.auth.data.googlesignin.GoogleSignInEventListener +import com.google.android.horologist.auth.ui.googlesignin.signin.GoogleSignInViewModel +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuthException +import com.google.firebase.auth.GoogleAuthProvider +import com.google.firebase.auth.auth +import fr.paug.androidmakers.wear.R +import fr.paug.androidmakers.wear.applicationContext +import kotlinx.coroutines.tasks.await + +private val TAG = GoogleSignInViewModel::class.java.simpleName + +class GoogleSignInViewModel( + onSignInSuccess: () -> Unit, + onSignInFailed: () -> Unit, +) : GoogleSignInViewModel( + googleSignInClient = GoogleSignIn.getClient( + applicationContext, + GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(applicationContext.getString(R.string.default_web_client_id)) + .build() + ), + googleSignInEventListener = object : GoogleSignInEventListener { + override suspend fun onSignedIn(account: GoogleSignInAccount) { + Log.d(TAG, "Google sign in success") + val idToken = account.idToken + try { + val credential = GoogleAuthProvider.getCredential(idToken!!, null) + Firebase.auth.signInWithCredential(credential).await() + onSignInSuccess() + } catch (e: FirebaseAuthException) { + Log.w(TAG, "Could not get Firebase auth credential from Google id token", e) + onSignInFailed() + } + } + } +) 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 new file mode 100644 index 00000000..8c74ce85 --- /dev/null +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/signin/SignInScreen.kt @@ -0,0 +1,47 @@ +package fr.paug.androidmakers.wear.ui.signin + +import android.util.Log +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.google.android.horologist.auth.composables.dialogs.SignedInConfirmationDialog +import com.google.android.horologist.auth.composables.screens.AuthErrorScreen +import com.google.android.horologist.auth.ui.googlesignin.signin.GoogleSignInScreen +import com.google.android.horologist.compose.layout.ScreenScaffold + +private const val TAG = "SignInScreen" + +@Composable +fun SignInScreen( + onSignInSuccess: () -> Unit, + onDismissOrTimeout: () -> Unit, +) { + ScreenScaffold { + GoogleSignInScreen( + modifier = Modifier.fillMaxSize(), + onAuthCancelled = { + Log.d(TAG, "onAuthCancelled") + }, + failedContent = { + AuthErrorScreen() + }, + viewModel = GoogleSignInViewModel( + onSignInSuccess = { + onSignInSuccess() + }, + onSignInFailed = { + Log.d(TAG, "onSignInFailed") + } + ) + ) { successState -> + SignedInConfirmationDialog( + modifier = Modifier.fillMaxSize(), + onDismissOrTimeout = { + Log.d(TAG, "onDismissOrTimeout") + onDismissOrTimeout() + }, + accountUiModel = successState.accountUiModel, + ) + } + } +} diff --git a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/theme/Color.kt b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/theme/Color.kt index db5567c3..a8f71e7a 100644 --- a/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/theme/Color.kt +++ b/wearApp/src/main/java/fr/paug/androidmakers/wear/ui/theme/Color.kt @@ -3,19 +3,16 @@ package fr.paug.androidmakers.wear.ui.theme import androidx.compose.ui.graphics.Color import androidx.wear.compose.material.Colors -val Purple200 = Color(0xFFBB86FC) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val Red400 = Color(0xFFCF6679) +val DroidConBlue700 = Color(0xFF1c11ea) +val DroidConBlue200 = Color(0xFF8e88f6) +val DroidConTeal200 = Color(0xFF5adec1) +val DroidConOrange400 = Color(0xFFf27752) val wearColorPalette: Colors = Colors( - primary = Purple200, - primaryVariant = Purple700, - secondary = Teal200, - secondaryVariant = Teal200, - error = Red400, - onPrimary = Color.Black, + primary = DroidConBlue700, + primaryVariant = DroidConBlue200, + secondary = DroidConTeal200, + secondaryVariant = DroidConOrange400, + onPrimary = Color.White, onSecondary = Color.Black, - onError = Color.Black ) diff --git a/wearApp/src/main/res/drawable/splash_icon.png b/wearApp/src/main/res/drawable/splash_icon.png new file mode 100644 index 00000000..d978952c Binary files /dev/null and b/wearApp/src/main/res/drawable/splash_icon.png differ diff --git a/wearApp/src/main/res/drawable/splash_icon.xml b/wearApp/src/main/res/drawable/splash_icon.xml deleted file mode 100644 index 44bfb7c4..00000000 --- a/wearApp/src/main/res/drawable/splash_icon.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - diff --git a/wearApp/src/main/res/mipmap-anydpi/ic_launcher.xml b/wearApp/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000..728b6f22 --- /dev/null +++ b/wearApp/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/wearApp/src/main/res/mipmap-hdpi/ic_launcher.webp b/wearApp/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78e..00000000 Binary files a/wearApp/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/wearApp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/wearApp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..33007ea2 Binary files /dev/null and b/wearApp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/wearApp/src/main/res/mipmap-mdpi/ic_launcher.webp b/wearApp/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64..00000000 Binary files a/wearApp/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/wearApp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/wearApp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..8715f9bc Binary files /dev/null and b/wearApp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/wearApp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/wearApp/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a3070..00000000 Binary files a/wearApp/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/wearApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/wearApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..d978952c Binary files /dev/null and b/wearApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77f..00000000 Binary files a/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..2c9cfa8c Binary files /dev/null and b/wearApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d6427..00000000 Binary files a/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..ec2a3605 Binary files /dev/null and b/wearApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/wearApp/src/main/res/values-fr/strings.xml b/wearApp/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..6be0480d --- /dev/null +++ b/wearApp/src/main/res/values-fr/strings.xml @@ -0,0 +1,10 @@ + + Jour 1 + Jour 2 + + Aucune session favorite + + Connexion + Déconnexion + Sessions favorites seulement + diff --git a/wearApp/src/main/res/values/strings.xml b/wearApp/src/main/res/values/strings.xml index b47e0764..82bb8081 100644 --- a/wearApp/src/main/res/values/strings.xml +++ b/wearApp/src/main/res/values/strings.xml @@ -1,4 +1,12 @@ - Android Makers by droidcon - Hello, World! + Android Makers by droidcon + + Day 1 + Day 2 + + No favorite sessions + + Sign in + Sign out + Show favorites only