diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index 2f601e2d50..5dd327d5c8 100644 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -1,23 +1,23 @@ -/* - * Copyright 2019 Google, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# - org.gradle.daemon=false - org.gradle.parallel=true - org.gradle.jvmargs=-Xmx5120m - org.gradle.workers.max=2 +org.gradle.daemon=false +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx5120m +org.gradle.workers.max=2 - kotlin.incremental=false - kotlin.compiler.execution.strategy=in-process +kotlin.incremental=false +kotlin.compiler.execution.strategy=in-process diff --git a/Crane/README.md b/Crane/README.md index 81a7af7dc1..8525d6afc8 100644 --- a/Crane/README.md +++ b/Crane/README.md @@ -32,6 +32,38 @@ implemented using a different Activity will be displayed. In there, you can see embedded in Compose and Compose buttons updating the Android View. Notice how you can also interact with the `MapView` seamlessly. +## Hilt + +Crane uses [Hilt][hilt] to manage its dependencies. The Hilt's ViewModel extension (with the +`@ViewModelInject` annotation) works perfectly with Compose's ViewModel integration (`viewModel()` +composable function) as you can see in the following snippet of code. `viewModel()` will +automatically use the factory that Hilt creates for the ViewModel: + +``` +class MainViewModel @ViewModelInject constructor( + private val destinationsRepository: DestinationsRepository, + @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher, + datesRepository: DatesRepository +) : ViewModel() { ... } + +@Composable +fun CraneHomeContent(...) { + val viewModel: MainViewModel = viewModel() + ... +} +``` + +Disclaimer: Passing dependencies to a ViewModel which are not available at compile time (which is +sometimes called _assisted injection_) doesn't work as you might expect using `viewModel()`. +Compose's ViewModel integration cannot currently scope a ViewModel to a given composable. Instead +it is always scoped to the host Activity or Fragment. This means that calling `viewModel()` with +different factories in the same host Activity/Fragment don't have the desired effect; the _first_ +factory will be always used. + +This is the case of the [DetailsViewModel](detailsViewModel), which takes the name of +a `City` as a parameter to load the required information for the screen. However, the above isn't a +problem in this sample, since `DetailsScreen` is always used in it's own newly launched Activity. + ## Google Maps SDK To get the MapView working, you need to get an API key as @@ -79,6 +111,8 @@ limitations under the License. [details]: app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt [data]: app/src/main/java/androidx/compose/samples/crane/data/CraneData.kt [mainViewModel]: app/src/main/java/androidx/compose/samples/crane/home/MainViewModel.kt +[detailsViewModel]: app/src/main/java/androidx/compose/samples/crane/details/DetailsViewModel.kt [homeTest]: app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt [detailsTest]: app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt [coil-accompanist]: https://github.com/chrisbanes/accompanist +[hilt]: https://d.android.com/hilt diff --git a/Crane/app/build.gradle b/Crane/app/build.gradle index a2d1d99f64..31f398c4c2 100644 --- a/Crane/app/build.gradle +++ b/Crane/app/build.gradle @@ -19,6 +19,8 @@ import com.example.crane.buildsrc.Libs plugins { id 'com.android.application' id 'kotlin-android' + id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' } // Reads the Google maps key that is used in the AndroidManifest @@ -36,7 +38,13 @@ android { versionCode 1 versionName "1.0" vectorDrawables.useSupportLibrary = true - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.compose.samples.crane.CustomTestRunner" + + javaCompileOptions { + annotationProcessorOptions { + arguments["dagger.hilt.disableModulesHaveInstallInCheck"] = "true" + } + } manifestPlaceholders = [ googleMapsKey : properties.getProperty("google.maps.key", "") ] } @@ -81,14 +89,22 @@ android { resValues false shaders false } + composeOptions { kotlinCompilerVersion Libs.Kotlin.version kotlinCompilerExtensionVersion Libs.AndroidX.Compose.version } + + packagingOptions { + exclude "META-INF/licenses/**" + exclude "META-INF/AL2.0" + exclude "META-INF/LGPL2.1" + } } dependencies { implementation Libs.Kotlin.stdlib + implementation Libs.Kotlin.Coroutines.android implementation Libs.googleMaps implementation Libs.AndroidX.Compose.runtime @@ -98,12 +114,27 @@ dependencies { implementation Libs.AndroidX.Compose.layout implementation Libs.AndroidX.Compose.animation implementation Libs.AndroidX.UI.tooling - implementation Libs.Accompanist.coil + implementation Libs.AndroidX.Lifecycle.viewModelKtx + implementation Libs.Hilt.android + implementation Libs.Hilt.AndroidX.viewModel + compileOnly Libs.AssistedInjection.dagger + kapt Libs.Hilt.compiler + kapt Libs.Hilt.AndroidX.compiler + kapt Libs.AssistedInjection.processor + + androidTestImplementation Libs.JUnit.junit androidTestImplementation Libs.AndroidX.Test.runner androidTestImplementation Libs.AndroidX.Test.espressoCore androidTestImplementation Libs.AndroidX.Test.rules androidTestImplementation Libs.AndroidX.Test.Ext.junit + androidTestImplementation Libs.Kotlin.Coroutines.test androidTestImplementation Libs.AndroidX.Compose.uiTest + androidTestImplementation Libs.Hilt.android + androidTestImplementation Libs.Hilt.AndroidX.viewModel + androidTestImplementation Libs.Hilt.testing + kaptAndroidTest Libs.Hilt.compiler + kaptAndroidTest Libs.Hilt.AndroidX.compiler + kaptAndroidTest Libs.AssistedInjection.processor } diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/CustomTestRunner.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/CustomTestRunner.kt new file mode 100644 index 0000000000..d81394f073 --- /dev/null +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/CustomTestRunner.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class CustomTestRunner : AndroidJUnitRunner() { + override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + } +} diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt index e45d6cb306..27105bac9c 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt @@ -17,66 +17,61 @@ package androidx.compose.samples.crane.calendar import androidx.compose.material.Surface -import androidx.compose.samples.crane.base.ServiceLocator -import androidx.compose.samples.crane.calendar.model.CalendarDay -import androidx.compose.samples.crane.calendar.model.CalendarMonth -import androidx.compose.samples.crane.calendar.model.DaySelected import androidx.compose.samples.crane.calendar.model.DaySelectedStatus import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.FirstDay import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.FirstLastDay import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.LastDay import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.NoSelected import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.Selected +import androidx.compose.samples.crane.data.DatesRepository +import androidx.compose.samples.crane.di.DispatchersModule import androidx.compose.samples.crane.ui.CraneTheme import androidx.ui.test.ComposeTestRule import androidx.ui.test.SemanticsMatcher import androidx.ui.test.assertLabelEquals -import androidx.ui.test.createComposeRule +import androidx.ui.test.createAndroidComposeRule import androidx.ui.test.onNodeWithLabel import androidx.ui.test.performClick import androidx.ui.test.performScrollTo -import org.junit.After +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules import org.junit.Before import org.junit.Rule import org.junit.Test +import javax.inject.Inject +@UninstallModules(DispatchersModule::class) +@HiltAndroidTest class CalendarTest { - @get:Rule - val composeTestRule = createComposeRule(disableTransitions = true) - - var dateSelected = "" - private val onDayClicked: (CalendarDay, CalendarMonth) -> Unit = { day, month -> - dateSelected = "${month.name} ${day.value}" - ServiceLocator.datesSelected.daySelected( - DaySelected( - day = day.value.toInt(), - month = month - ) - ) - } + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val composeTestRule = createAndroidComposeRule() + + @Inject + lateinit var datesRepository: DatesRepository @Before fun setUp() { + hiltRule.inject() + composeTestRule.setContent { CraneTheme { Surface { - Calendar(onDayClicked) + CalendarScreen(onBackPressed = {}) } } } } - @After - fun tearDown() { - ServiceLocator.datesSelected.clearDates() - } - @Test fun scrollsToTheBottom() { composeTestRule.onNodeWithLabel("January 1").assertExists() composeTestRule.onNodeWithLabel("December 31").performScrollTo().performClick() - assert(dateSelected == "December 31") + assert(datesRepository.datesSelected.toString() == "Dec 31") } @Test diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt index dc3de7710c..d61dc5db38 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt @@ -17,8 +17,10 @@ package androidx.compose.samples.crane.details import androidx.compose.samples.crane.R +import androidx.compose.samples.crane.data.DestinationsRepository import androidx.compose.samples.crane.data.ExploreModel import androidx.compose.samples.crane.data.MADRID +import androidx.compose.samples.crane.di.DispatchersModule import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -31,16 +33,30 @@ import androidx.ui.test.onNodeWithText import com.google.android.libraries.maps.MapView import com.google.android.libraries.maps.model.CameraPosition import com.google.android.libraries.maps.model.LatLng +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules +import org.junit.Before import org.junit.Rule import org.junit.Test import java.util.concurrent.CountDownLatch +import javax.inject.Inject +@UninstallModules(DispatchersModule::class) +@HiltAndroidTest class DetailsActivityTest { - private val expectedDescription = "description" - private val testExploreModel = ExploreModel(MADRID, expectedDescription, "imageUrl") + @Inject + lateinit var destinationsRepository: DestinationsRepository + lateinit var cityDetails: ExploreModel - @get:Rule + private val city = MADRID + private val testExploreModel = ExploreModel(city, "description", "imageUrl") + + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) val composeTestRule = AndroidComposeTestRule( ActivityScenarioRule( createDetailsActivityIntent( @@ -50,10 +66,16 @@ class DetailsActivityTest { ) ) + @Before + fun setUp() { + hiltRule.inject() + cityDetails = destinationsRepository.getDestination(MADRID.name)!! + } + @Test fun mapView_cameraPositioned() { - composeTestRule.onNodeWithText(MADRID.nameToDisplay).assertIsDisplayed() - composeTestRule.onNodeWithText(expectedDescription).assertIsDisplayed() + composeTestRule.onNodeWithText(cityDetails.city.nameToDisplay).assertIsDisplayed() + composeTestRule.onNodeWithText(cityDetails.description).assertIsDisplayed() onView(withId(R.id.map)).check(matches(isDisplayed())) var cameraPosition: CameraPosition? = null diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/di/TestDispatchersModule.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/di/TestDispatchersModule.kt new file mode 100644 index 0000000000..69ab93f999 --- /dev/null +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/di/TestDispatchersModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("DEPRECATION") + +package androidx.compose.samples.crane.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ApplicationComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +@Module +@InstallIn(ApplicationComponent::class) +class TestDispatchersModule { + + @Provides + @DefaultDispatcher + fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Unconfined +} diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt index e5ceec480f..97095b1e06 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt @@ -17,18 +17,27 @@ package androidx.compose.samples.crane.home import androidx.compose.material.Surface +import androidx.compose.samples.crane.di.DispatchersModule import androidx.compose.samples.crane.ui.CraneTheme -import androidx.ui.test.createComposeRule +import androidx.ui.test.createAndroidComposeRule import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules import org.junit.Before import org.junit.Rule import org.junit.Test +@UninstallModules(DispatchersModule::class) +@HiltAndroidTest class HomeTest { - @get:Rule - val composeTestRule = createComposeRule(disableTransitions = true) + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val composeTestRule = createAndroidComposeRule() @Before fun setUp() { diff --git a/Crane/app/src/main/AndroidManifest.xml b/Crane/app/src/main/AndroidManifest.xml index 141d1c4998..300a8a0503 100644 --- a/Crane/app/src/main/AndroidManifest.xml +++ b/Crane/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + */ +sealed class Result { + data class Success(val data: T) : Result() + data class Error(val exception: Exception) : Result() +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt index b785cbe137..af3590d99c 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt @@ -36,11 +36,12 @@ import androidx.compose.material.Colors import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.Composable -import androidx.compose.samples.crane.calendar.data.year2020 import androidx.compose.samples.crane.calendar.model.CalendarDay import androidx.compose.samples.crane.calendar.model.CalendarMonth import androidx.compose.samples.crane.calendar.model.DayOfWeek import androidx.compose.samples.crane.calendar.model.DaySelectedStatus +import androidx.compose.samples.crane.data.CalendarYear +import androidx.compose.samples.crane.data.DatesLocalDataSource import androidx.compose.samples.crane.ui.CraneTheme import androidx.compose.samples.crane.util.Circle import androidx.compose.samples.crane.util.SemiRect @@ -58,12 +59,13 @@ typealias CalendarWeek = List @Composable fun Calendar( + calendarYear: CalendarYear, onDayClicked: (CalendarDay, CalendarMonth) -> Unit, modifier: Modifier = Modifier ) { ScrollableColumn(modifier = modifier) { Spacer(Modifier.preferredHeight(32.dp)) - for (month in year2020) { + for (month in calendarYear) { Month(month = month, onDayClicked = onDayClicked) Spacer(Modifier.preferredHeight(32.dp)) } @@ -280,6 +282,6 @@ var SemanticsPropertyReceiver.dayStatusProperty by DayStatusKey @Composable fun DayPreview() { CraneTheme { - Calendar(onDayClicked = { _, _ -> }) + Calendar(DatesLocalDataSource().year2020, onDayClicked = { _, _ -> }) } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt index bdfb166bb7..1ac1d93dde 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt @@ -30,18 +30,21 @@ import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.samples.crane.R import androidx.compose.samples.crane.base.CraneScaffold -import androidx.compose.samples.crane.base.ServiceLocator import androidx.compose.samples.crane.calendar.model.CalendarDay import androidx.compose.samples.crane.calendar.model.CalendarMonth import androidx.compose.samples.crane.calendar.model.DaySelected +import androidx.compose.samples.crane.data.CalendarYear import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.viewinterop.viewModel +import dagger.hilt.android.AndroidEntryPoint fun launchCalendarActivity(context: Context) { val intent = Intent(context, CalendarActivity::class.java) context.startActivity(intent) } +@AndroidEntryPoint class CalendarActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -57,27 +60,37 @@ class CalendarActivity : ComponentActivity() { } } -// Extracted out to a separate variable. If this lambda is used as a trailing lambda in the -// Calendar function, it recomposes the whole Calendar view when clicked on it. -private val onDayClicked: (CalendarDay, CalendarMonth) -> Unit = { calendarDay, calendarMonth -> - ServiceLocator.datesSelected.daySelected( - DaySelected( - day = calendarDay.value.toInt(), - month = calendarMonth - ) +@Composable +fun CalendarScreen(onBackPressed: () -> Unit) { + val calendarViewModel: CalendarViewModel = viewModel() + val calendarYear = calendarViewModel.calendarYear + + CalendarContent( + selectedDates = calendarViewModel.datesSelected.toString(), + calendarYear = calendarYear, + onDayClicked = { calendarDay, calendarMonth -> + calendarViewModel.onDaySelected( + DaySelected(calendarDay.value.toInt(), calendarMonth, calendarYear) + ) + }, + onBackPressed = onBackPressed ) } @Composable -fun CalendarScreen(onBackPressed: () -> Unit) { +private fun CalendarContent( + selectedDates: String, + calendarYear: CalendarYear, + onDayClicked: (CalendarDay, CalendarMonth) -> Unit, + onBackPressed: () -> Unit +) { CraneScaffold { Column { - val selectedDatesText = ServiceLocator.datesSelected.toString() TopAppBar( title = { Text( - text = if (selectedDatesText.isEmpty()) "Select Dates" - else selectedDatesText + text = if (selectedDates.isEmpty()) "Select Dates" + else selectedDates ) }, navigationIcon = { @@ -88,7 +101,7 @@ fun CalendarScreen(onBackPressed: () -> Unit) { backgroundColor = MaterialTheme.colors.primaryVariant ) Surface { - Calendar(onDayClicked = onDayClicked) + Calendar(calendarYear, onDayClicked) } } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarViewModel.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarViewModel.kt new file mode 100644 index 0000000000..1cee9585b6 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarViewModel.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.calendar + +import androidx.compose.samples.crane.calendar.model.DaySelected +import androidx.compose.samples.crane.data.DatesRepository +import androidx.hilt.lifecycle.ViewModelInject +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch + +class CalendarViewModel @ViewModelInject constructor( + private val datesRepository: DatesRepository +) : ViewModel() { + + val datesSelected = datesRepository.datesSelected + val calendarYear = datesRepository.calendarYear + + fun onDaySelected(daySelected: DaySelected) { + viewModelScope.launch { + datesRepository.onDaySelected(daySelected) + } + } +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/data/CalendarData.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/data/CalendarData.kt deleted file mode 100644 index 3152d07d8b..0000000000 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/data/CalendarData.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.samples.crane.calendar.data - -import androidx.compose.samples.crane.calendar.model.CalendarMonth -import androidx.compose.samples.crane.calendar.model.DayOfWeek - -val january2020 = CalendarMonth( - name = "January", - year = "2020", - numDays = 31, - monthNumber = 1, - startDayOfWeek = DayOfWeek.Wednesday -) -val february2020 = CalendarMonth( - name = "February", - year = "2020", - numDays = 29, - monthNumber = 2, - startDayOfWeek = DayOfWeek.Saturday -) -val march2020 = CalendarMonth( - name = "March", - year = "2020", - numDays = 31, - monthNumber = 3, - startDayOfWeek = DayOfWeek.Sunday -) -val april2020 = CalendarMonth( - name = "April", - year = "2020", - numDays = 30, - monthNumber = 4, - startDayOfWeek = DayOfWeek.Wednesday -) -val may2020 = CalendarMonth( - name = "May", - year = "2020", - numDays = 31, - monthNumber = 5, - startDayOfWeek = DayOfWeek.Friday -) -val june2020 = CalendarMonth( - name = "June", - year = "2020", - numDays = 30, - monthNumber = 6, - startDayOfWeek = DayOfWeek.Monday -) -val july2020 = CalendarMonth( - name = "July", - year = "2020", - numDays = 31, - monthNumber = 7, - startDayOfWeek = DayOfWeek.Wednesday -) -val august2020 = CalendarMonth( - name = "August", - year = "2020", - numDays = 31, - monthNumber = 8, - startDayOfWeek = DayOfWeek.Saturday -) -val september2020 = CalendarMonth( - name = "September", - year = "2020", - numDays = 30, - monthNumber = 9, - startDayOfWeek = DayOfWeek.Tuesday -) -val october2020 = CalendarMonth( - name = "October", - year = "2020", - numDays = 31, - monthNumber = 10, - startDayOfWeek = DayOfWeek.Thursday -) -val november2020 = CalendarMonth( - name = "November", - year = "2020", - numDays = 30, - monthNumber = 11, - startDayOfWeek = DayOfWeek.Sunday -) -val december2020 = CalendarMonth( - name = "December", - year = "2020", - numDays = 31, - monthNumber = 12, - startDayOfWeek = DayOfWeek.Tuesday -) - -val year2020 = listOf( - january2020, - february2020, - march2020, - april2020, - may2020, - june2020, - july2020, - august2020, - september2020, - october2020, - november2020, - december2020 -) diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DatesSelectedState.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DatesSelectedState.kt index b27e45e3be..8d4cfc64dd 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DatesSelectedState.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DatesSelectedState.kt @@ -20,9 +20,9 @@ import androidx.annotation.VisibleForTesting import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.samples.crane.calendar.data.year2020 +import androidx.compose.samples.crane.data.CalendarYear -class DatesSelectedState { +class DatesSelectedState(private val year: CalendarYear) { private var from by mutableStateOf(DaySelectedEmpty) private var to by mutableStateOf(DaySelectedEmpty) @@ -40,8 +40,6 @@ class DatesSelectedState { setDates(newDate, DaySelectedEmpty) } else if (from != DaySelectedEmpty && to != DaySelectedEmpty) { clearDates() - from = DaySelectedEmpty - to = DaySelectedEmpty daySelected(newDate = newDate) } else if (from == DaySelectedEmpty) { if (newDate < to) setDates(newDate, to) @@ -75,7 +73,7 @@ class DatesSelectedState { from.month.getDay(from.month.numDays).status = DaySelectedStatus.LastDay // Fill in-between months for (i in (from.month.monthNumber + 1) until to.month.monthNumber) { - val month = year2020[i - 1] + val month = year[i - 1] month.getDay(1).status = DaySelectedStatus.FirstDay for (j in 2 until month.numDays) { month.getDay(j).status = DaySelectedStatus.Selected @@ -92,7 +90,7 @@ class DatesSelectedState { @VisibleForTesting fun clearDates() { - if (from != DaySelectedEmpty && to != DaySelectedEmpty) { + if (from != DaySelectedEmpty || to != DaySelectedEmpty) { // Unselect dates from the same month if (from.month == to.month) { for (i in from.day..to.day) @@ -104,7 +102,7 @@ class DatesSelectedState { } // Fill in-between months for (i in (from.month.monthNumber + 1) until to.month.monthNumber) { - val month = year2020[i - 1] + val month = year[i - 1] for (j in 1..month.numDays) { month.getDay(j).status = DaySelectedStatus.NoSelected } @@ -115,5 +113,9 @@ class DatesSelectedState { } } } + from.calendarDay.value.status = DaySelectedStatus.NoSelected + from = DaySelectedEmpty + to.calendarDay.value.status = DaySelectedStatus.NoSelected + to = DaySelectedEmpty } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DaySelected.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DaySelected.kt index 85077cbde4..4dfd0a287c 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DaySelected.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/model/DaySelected.kt @@ -16,10 +16,9 @@ package androidx.compose.samples.crane.calendar.model -import androidx.compose.samples.crane.calendar.data.january2020 -import androidx.compose.samples.crane.calendar.data.year2020 +import androidx.compose.samples.crane.data.CalendarYear -data class DaySelected(val day: Int, val month: CalendarMonth) { +data class DaySelected(val day: Int, val month: CalendarMonth, val year: CalendarYear) { val calendarDay = lazy { month.getDay(day) } @@ -31,8 +30,8 @@ data class DaySelected(val day: Int, val month: CalendarMonth) { operator fun compareTo(other: DaySelected): Int { if (day == other.day && month == other.month) return 0 if (month == other.month) return day.compareTo(other.day) - return (year2020.indexOf(month)).compareTo( - year2020.indexOf(other.month) + return (year.indexOf(month)).compareTo( + year.indexOf(other.month) ) } } @@ -40,4 +39,4 @@ data class DaySelected(val day: Int, val month: CalendarMonth) { /** * Represents an empty value for [DaySelected] */ -val DaySelectedEmpty = DaySelected(-1, january2020) +val DaySelectedEmpty = DaySelected(-1, CalendarMonth("", "", 0, 0, DayOfWeek.Sunday), emptyList()) diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/data/CraneData.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/data/CraneData.kt deleted file mode 100644 index 27ba5b9135..0000000000 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/data/CraneData.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.samples.crane.data - -private const val DEFAULT_IMAGE_WIDTH = "250" - -val craneRestaurants = listOf( - ExploreModel( - city = NAPLES, - description = "1286 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1534308983496-4fabb1a015ee?ixlib=rb-1.2.1&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = DALLAS, - description = "2241 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1495749388945-9d6e4e5b67b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = CORDOBA, - description = "876 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1562625964-ffe9b2f617fc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=250&q=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = MADRID, - description = "5610 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1515443961218-a51367888e4b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = MALDIVAS, - description = "1286 Restaurants", - imageUrl = "https://images.unsplash.com/flagged/photo-1556202256-af2687079e51?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = ASPEN, - description = "2241 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1542384557-0824d90731ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = BALI, - description = "876 Restaurants", - imageUrl = "https://images.unsplash.com/photo-1567337710282-00832b415979?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ) -) - -val craneHotels = listOf( - ExploreModel( - city = MALDIVAS, - description = "1286 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = ASPEN, - description = "2241 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1445019980597-93fa8acb246c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = BALI, - description = "876 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1570213489059-0aac6626cade?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = BIGSUR, - description = "5610 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1561409037-c7be81613c1f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = NAPLES, - description = "1286 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1455587734955-081b22074882?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = DALLAS, - description = "2241 Available Properties", - imageUrl = "https://images.unsplash.com/46/sh3y2u5PSaKq8c4LxB3B_submission-photo-4.jpg?ixlib=rb-1.2.1&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = CORDOBA, - description = "876 Available Properties", - imageUrl = "https://images.unsplash.com/photo-1570214476695-19bd467e6f7a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ) -) - -val craneDestinations = listOf( - ExploreModel( - city = KHUMBUVALLEY, - description = "Nonstop - 5h 16m+", - imageUrl = "https://images.unsplash.com/photo-1544735716-392fe2489ffa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = MADRID, - description = "Nonstop - 2h 12m+", - imageUrl = "https://images.unsplash.com/photo-1539037116277-4db20889f2d4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = BALI, - description = "Nonstop - 6h 20m+", - imageUrl = "https://images.unsplash.com/photo-1518548419970-58e3b4079ab2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = ROME, - description = "Nonstop - 2h 38m+", - imageUrl = "https://images.unsplash.com/photo-1515542622106-78bda8ba0e5b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = GRANADA, - description = "Nonstop - 2h 12m+", - imageUrl = "https://images.unsplash.com/photo-1534423839368-1796a4dd1845?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = MALDIVAS, - description = "Nonstop - 9h 24m+", - imageUrl = "https://images.unsplash.com/photo-1544550581-5f7ceaf7f992?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = WASHINGTONDC, - description = "Nonstop - 7h 30m+", - imageUrl = "https://images.unsplash.com/photo-1557160854-e1e89fdd3286?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = BARCELONA, - description = "Nonstop - 2h 12m+", - imageUrl = "https://images.unsplash.com/photo-1562883676-8c7feb83f09b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ), - ExploreModel( - city = CRETE, - description = "Nonstop - 1h 50m+", - imageUrl = "https://images.unsplash.com/photo-1486575008575-27670acb58db?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" - ) -) diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesLocalDataSource.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesLocalDataSource.kt new file mode 100644 index 0000000000..d33dd24959 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesLocalDataSource.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.data + +import androidx.compose.samples.crane.calendar.model.CalendarDay +import androidx.compose.samples.crane.calendar.model.CalendarMonth +import androidx.compose.samples.crane.calendar.model.DayOfWeek +import javax.inject.Inject +import javax.inject.Singleton + +typealias CalendarYear = List + +/** + * Annotated with Singleton because [CalendarDay] contains mutable state. + */ +@Singleton +class DatesLocalDataSource @Inject constructor() { + + private val january2020 = CalendarMonth( + name = "January", + year = "2020", + numDays = 31, + monthNumber = 1, + startDayOfWeek = DayOfWeek.Wednesday + ) + private val february2020 = CalendarMonth( + name = "February", + year = "2020", + numDays = 29, + monthNumber = 2, + startDayOfWeek = DayOfWeek.Saturday + ) + private val march2020 = CalendarMonth( + name = "March", + year = "2020", + numDays = 31, + monthNumber = 3, + startDayOfWeek = DayOfWeek.Sunday + ) + private val april2020 = CalendarMonth( + name = "April", + year = "2020", + numDays = 30, + monthNumber = 4, + startDayOfWeek = DayOfWeek.Wednesday + ) + private val may2020 = CalendarMonth( + name = "May", + year = "2020", + numDays = 31, + monthNumber = 5, + startDayOfWeek = DayOfWeek.Friday + ) + private val june2020 = CalendarMonth( + name = "June", + year = "2020", + numDays = 30, + monthNumber = 6, + startDayOfWeek = DayOfWeek.Monday + ) + private val july2020 = CalendarMonth( + name = "July", + year = "2020", + numDays = 31, + monthNumber = 7, + startDayOfWeek = DayOfWeek.Wednesday + ) + private val august2020 = CalendarMonth( + name = "August", + year = "2020", + numDays = 31, + monthNumber = 8, + startDayOfWeek = DayOfWeek.Saturday + ) + private val september2020 = CalendarMonth( + name = "September", + year = "2020", + numDays = 30, + monthNumber = 9, + startDayOfWeek = DayOfWeek.Tuesday + ) + private val october2020 = CalendarMonth( + name = "October", + year = "2020", + numDays = 31, + monthNumber = 10, + startDayOfWeek = DayOfWeek.Thursday + ) + private val november2020 = CalendarMonth( + name = "November", + year = "2020", + numDays = 30, + monthNumber = 11, + startDayOfWeek = DayOfWeek.Sunday + ) + private val december2020 = CalendarMonth( + name = "December", + year = "2020", + numDays = 31, + monthNumber = 12, + startDayOfWeek = DayOfWeek.Tuesday + ) + + val year2020: CalendarYear = listOf( + january2020, + february2020, + march2020, + april2020, + may2020, + june2020, + july2020, + august2020, + september2020, + october2020, + november2020, + december2020 + ) +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesRepository.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesRepository.kt new file mode 100644 index 0000000000..7b0dc0e7c5 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DatesRepository.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.data + +import androidx.compose.samples.crane.calendar.model.DatesSelectedState +import androidx.compose.samples.crane.calendar.model.DaySelected +import androidx.compose.samples.crane.di.DefaultDispatcher +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Annotated with Singleton because [DatesSelectedState] contains mutable state. + */ +@Singleton +class DatesRepository @Inject constructor( + datesLocalDataSource: DatesLocalDataSource, + @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher, + +) { + val calendarYear = datesLocalDataSource.year2020 + val datesSelected = DatesSelectedState(datesLocalDataSource.year2020) + + suspend fun onDaySelected(daySelected: DaySelected) = withContext(defaultDispatcher) { + datesSelected.daySelected(daySelected) + } +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsLocalDataSource.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsLocalDataSource.kt new file mode 100644 index 0000000000..bb5d179182 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsLocalDataSource.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.data + +import javax.inject.Inject +import javax.inject.Singleton + +private const val DEFAULT_IMAGE_WIDTH = "250" + +/** + * Annotated with Singleton as the class created a lot of objects. + */ +@Singleton +class DestinationsLocalDataSource @Inject constructor() { + + val craneRestaurants = listOf( + ExploreModel( + city = NAPLES, + description = "1286 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1534308983496-4fabb1a015ee?ixlib=rb-1.2.1&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = DALLAS, + description = "2241 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1495749388945-9d6e4e5b67b1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = CORDOBA, + description = "876 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1562625964-ffe9b2f617fc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=250&q=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = MADRID, + description = "5610 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1515443961218-a51367888e4b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = MALDIVAS, + description = "1286 Restaurants", + imageUrl = "https://images.unsplash.com/flagged/photo-1556202256-af2687079e51?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = ASPEN, + description = "2241 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1542384557-0824d90731ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = BALI, + description = "876 Restaurants", + imageUrl = "https://images.unsplash.com/photo-1567337710282-00832b415979?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ) + ) + + val craneHotels = listOf( + ExploreModel( + city = MALDIVAS, + description = "1286 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = ASPEN, + description = "2241 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1445019980597-93fa8acb246c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = BALI, + description = "876 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1570213489059-0aac6626cade?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = BIGSUR, + description = "5610 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1561409037-c7be81613c1f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = NAPLES, + description = "1286 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1455587734955-081b22074882?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = DALLAS, + description = "2241 Available Properties", + imageUrl = "https://images.unsplash.com/46/sh3y2u5PSaKq8c4LxB3B_submission-photo-4.jpg?ixlib=rb-1.2.1&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = CORDOBA, + description = "876 Available Properties", + imageUrl = "https://images.unsplash.com/photo-1570214476695-19bd467e6f7a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ) + ) + + val craneDestinations = listOf( + ExploreModel( + city = KHUMBUVALLEY, + description = "Nonstop - 5h 16m+", + imageUrl = "https://images.unsplash.com/photo-1544735716-392fe2489ffa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = MADRID, + description = "Nonstop - 2h 12m+", + imageUrl = "https://images.unsplash.com/photo-1539037116277-4db20889f2d4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = BALI, + description = "Nonstop - 6h 20m+", + imageUrl = "https://images.unsplash.com/photo-1518548419970-58e3b4079ab2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = ROME, + description = "Nonstop - 2h 38m+", + imageUrl = "https://images.unsplash.com/photo-1515542622106-78bda8ba0e5b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = GRANADA, + description = "Nonstop - 2h 12m+", + imageUrl = "https://images.unsplash.com/photo-1534423839368-1796a4dd1845?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = MALDIVAS, + description = "Nonstop - 9h 24m+", + imageUrl = "https://images.unsplash.com/photo-1544550581-5f7ceaf7f992?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = WASHINGTONDC, + description = "Nonstop - 7h 30m+", + imageUrl = "https://images.unsplash.com/photo-1557160854-e1e89fdd3286?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = BARCELONA, + description = "Nonstop - 2h 12m+", + imageUrl = "https://images.unsplash.com/photo-1562883676-8c7feb83f09b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ), + ExploreModel( + city = CRETE, + description = "Nonstop - 1h 50m+", + imageUrl = "https://images.unsplash.com/photo-1486575008575-27670acb58db?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=$DEFAULT_IMAGE_WIDTH" + ) + ) +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsRepository.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsRepository.kt new file mode 100644 index 0000000000..df419033a4 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/data/DestinationsRepository.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.data + +import javax.inject.Inject + +class DestinationsRepository @Inject constructor( + private val destinationsLocalDataSource: DestinationsLocalDataSource +) { + val destinations: List = destinationsLocalDataSource.craneDestinations + val hotels: List = destinationsLocalDataSource.craneHotels + val restaurants: List = destinationsLocalDataSource.craneRestaurants + + fun getDestination(cityName: String): ExploreModel? { + return destinationsLocalDataSource.craneDestinations.firstOrNull { + it.city.name == cityName + } + } +} diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt index 4715a32dc5..6aaead5a75 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt @@ -30,12 +30,15 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.Button +import androidx.compose.material.ButtonConstants import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.runtime.savedinstancestate.savedInstanceState import androidx.compose.runtime.setValue import androidx.compose.samples.crane.base.CraneScaffold +import androidx.compose.samples.crane.base.Result import androidx.compose.samples.crane.data.ExploreModel import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -46,11 +49,11 @@ import com.google.android.libraries.maps.CameraUpdateFactory import com.google.android.libraries.maps.MapView import com.google.android.libraries.maps.model.LatLng import com.google.android.libraries.maps.model.MarkerOptions +import dagger.hilt.android.AndroidEntryPoint +import java.lang.IllegalStateException +import javax.inject.Inject -private const val DETAILS_NAME = "DETAILS_NAME" -private const val DETAILS_DESCRIPTION = "DETAILS_DESCRIPTION" -private const val DETAILS_LATITUDE = "DETAILS_LATITUDE" -private const val DETAILS_LONGITUDE = "DETAILS_LONGITUDE" +private const val KEY_ARG_DETAILS_CITY_NAME = "KEY_ARG_DETAILS_CITY_NAME" fun launchDetailsActivity(context: Context, item: ExploreModel) { context.startActivity(createDetailsActivityIntent(context, item)) @@ -59,56 +62,72 @@ fun launchDetailsActivity(context: Context, item: ExploreModel) { @VisibleForTesting fun createDetailsActivityIntent(context: Context, item: ExploreModel): Intent { val intent = Intent(context, DetailsActivity::class.java) - intent.putExtra(DETAILS_NAME, item.city.nameToDisplay) - intent.putExtra(DETAILS_DESCRIPTION, item.description) - intent.putExtra(DETAILS_LATITUDE, item.city.latitude) - intent.putExtra(DETAILS_LONGITUDE, item.city.longitude) + intent.putExtra(KEY_ARG_DETAILS_CITY_NAME, item.city.name) return intent } data class DetailsActivityArg( - val name: String, - val description: String, - val latitude: String, - val longitude: String + val cityName: String ) +@AndroidEntryPoint class DetailsActivity : ComponentActivity() { + @Inject + lateinit var viewModelFactory: DetailsViewModel.AssistedFactory + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - val args = DetailsActivityArg( - name = intent.getStringExtra(DETAILS_NAME)!!, - description = intent.getStringExtra(DETAILS_DESCRIPTION)!!, - latitude = intent.getStringExtra(DETAILS_LATITUDE)!!, - longitude = intent.getStringExtra(DETAILS_LONGITUDE)!! - ) + val args = getDetailsArgs(intent) setContent { CraneScaffold { - DetailsScreen(args = args) + DetailsScreen(args, viewModelFactory, onErrorLoading = { finish() }) } } } + + private fun getDetailsArgs(intent: Intent): DetailsActivityArg { + val cityArg = intent.getStringExtra(KEY_ARG_DETAILS_CITY_NAME) + if (cityArg.isNullOrEmpty()) { + throw IllegalStateException("DETAILS_CITY_NAME arg cannot be null or empty") + } + return DetailsActivityArg(cityArg) + } +} + +@Composable +fun DetailsScreen( + args: DetailsActivityArg, + viewModelFactory: DetailsViewModel.AssistedFactory, + onErrorLoading: () -> Unit +) { + val viewModel: DetailsViewModel = viewModelFactory.create(args.cityName) + + val cityDetailsResult = remember(viewModel) { viewModel.cityDetails } + if (cityDetailsResult is Result.Success) { + DetailsContent(cityDetailsResult.data) + } else { + onErrorLoading() + } } @Composable -fun DetailsScreen(args: DetailsActivityArg) { +fun DetailsContent(exploreModel: ExploreModel) { Column(verticalArrangement = Arrangement.Center) { Spacer(Modifier.preferredHeight(32.dp)) Text( modifier = Modifier.align(Alignment.CenterHorizontally), - text = args.name, + text = exploreModel.city.nameToDisplay, style = MaterialTheme.typography.h4 ) Text( modifier = Modifier.align(Alignment.CenterHorizontally), - text = args.description, + text = exploreModel.description, style = MaterialTheme.typography.h6 ) Spacer(Modifier.preferredHeight(16.dp)) - CityMapView(args.latitude, args.longitude) + CityMapView(exploreModel.city.latitude, exploreModel.city.longitude) } } @@ -163,8 +182,10 @@ private fun ZoomControls( private fun ZoomButton(text: String, onClick: () -> Unit) { Button( modifier = Modifier.padding(8.dp), - backgroundColor = MaterialTheme.colors.onPrimary, - contentColor = MaterialTheme.colors.primary, + colors = ButtonConstants.defaultButtonColors( + backgroundColor = MaterialTheme.colors.onPrimary, + contentColor = MaterialTheme.colors.primary + ), onClick = onClick ) { Text(text = text, style = MaterialTheme.typography.h5) diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsViewModel.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsViewModel.kt new file mode 100644 index 0000000000..c97ffe1407 --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsViewModel.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.details + +import androidx.compose.samples.crane.base.Result +import androidx.compose.samples.crane.data.DestinationsRepository +import androidx.compose.samples.crane.data.ExploreModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import com.squareup.inject.assisted.dagger2.AssistedModule +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import java.lang.IllegalArgumentException + +class DetailsViewModel @AssistedInject constructor( + private val destinationsRepository: DestinationsRepository, + @Assisted private val cityName: String +) : ViewModel() { + + val cityDetails: Result + get() { + val destination = destinationsRepository.getDestination(cityName) + return if (destination != null) { + Result.Success(destination) + } else { + Result.Error(IllegalArgumentException("City doesn't exist")) + } + } + + @AssistedInject.Factory + interface AssistedFactory { + fun create(cityName: String): DetailsViewModel + } + + @Suppress("UNCHECKED_CAST") + companion object { + fun provideFactory( + assistedFactory: AssistedFactory, + cityName: String + ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return assistedFactory.create(cityName) as T + } + } + } +} + +@InstallIn(ActivityRetainedComponent::class) +@AssistedModule +@Module(includes = [AssistedInject_AssistedInjectModule::class]) +interface AssistedInjectModule diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/di/DispatchersModule.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/di/DispatchersModule.kt new file mode 100644 index 0000000000..19ff0c9f6b --- /dev/null +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/di/DispatchersModule.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.samples.crane.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ApplicationComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import javax.inject.Qualifier + +@Module +@InstallIn(ApplicationComponent::class) +class DispatchersModule { + + @Provides + @DefaultDispatcher + fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default +} + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class DefaultDispatcher diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/CraneHome.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/CraneHome.kt index 510c72769f..570e36cc2c 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/CraneHome.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/CraneHome.kt @@ -154,8 +154,13 @@ private fun SearchContent( onDateSelectionClicked: () -> Unit, onExploreItemClicked: OnExploreItemClicked ) { + // Reading datesSelected State from here instead of passing the String from the ViewModel + // to cause a recomposition when the dates change. + val datesSelected = viewModel.datesSelected.toString() + when (tabSelected) { CraneScreen.Fly -> FlySearchContent( + datesSelected, searchUpdates = FlySearchContentUpdates( onPeopleChanged = onPeopleChanged, onToDestinationChanged = { viewModel.toDestinationChanged(it) }, @@ -164,6 +169,7 @@ private fun SearchContent( ) ) CraneScreen.Sleep -> SleepSearchContent( + datesSelected, sleepUpdates = SleepSearchContentUpdates( onPeopleChanged = onPeopleChanged, onDateSelectionClicked = onDateSelectionClicked, @@ -171,6 +177,7 @@ private fun SearchContent( ) ) CraneScreen.Eat -> EatSearchContent( + datesSelected, eatUpdates = EatSearchContentUpdates( onPeopleChanged = onPeopleChanged, onDateSelectionClicked = onDateSelectionClicked, diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/HomeFeatures.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/HomeFeatures.kt index 58b3051203..64a6f46479 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/HomeFeatures.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/HomeFeatures.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable -fun FlySearchContent(searchUpdates: FlySearchContentUpdates) { +fun FlySearchContent(datesSelected: String, searchUpdates: FlySearchContentUpdates) { CraneSearch { PeopleUserInput( titleSuffix = ", Economy", @@ -38,27 +38,27 @@ fun FlySearchContent(searchUpdates: FlySearchContentUpdates) { Spacer(Modifier.preferredHeight(8.dp)) ToDestinationUserInput(onToDestinationChanged = searchUpdates.onToDestinationChanged) Spacer(Modifier.preferredHeight(8.dp)) - DatesUserInput(onDateSelectionClicked = searchUpdates.onDateSelectionClicked) + DatesUserInput(datesSelected, onDateSelectionClicked = searchUpdates.onDateSelectionClicked) } } @Composable -fun SleepSearchContent(sleepUpdates: SleepSearchContentUpdates) { +fun SleepSearchContent(datesSelected: String, sleepUpdates: SleepSearchContentUpdates) { CraneSearch { PeopleUserInput(onPeopleChanged = { sleepUpdates.onPeopleChanged }) Spacer(Modifier.preferredHeight(8.dp)) - DatesUserInput(onDateSelectionClicked = sleepUpdates.onDateSelectionClicked) + DatesUserInput(datesSelected, onDateSelectionClicked = sleepUpdates.onDateSelectionClicked) Spacer(Modifier.preferredHeight(8.dp)) SimpleUserInput(caption = "Select Location", vectorImageId = R.drawable.ic_hotel) } } @Composable -fun EatSearchContent(eatUpdates: EatSearchContentUpdates) { +fun EatSearchContent(datesSelected: String, eatUpdates: EatSearchContentUpdates) { CraneSearch { PeopleUserInput(onPeopleChanged = { eatUpdates.onPeopleChanged }) Spacer(Modifier.preferredHeight(8.dp)) - DatesUserInput(onDateSelectionClicked = eatUpdates.onDateSelectionClicked) + DatesUserInput(datesSelected, onDateSelectionClicked = eatUpdates.onDateSelectionClicked) Spacer(Modifier.preferredHeight(8.dp)) SimpleUserInput(caption = "Select Time", vectorImageId = R.drawable.ic_time) Spacer(Modifier.preferredHeight(8.dp)) diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt index a8cf1717de..b22e9eda1a 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt @@ -42,7 +42,9 @@ import androidx.compose.ui.draw.drawOpacity import androidx.compose.ui.platform.setContent import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainViewModel.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainViewModel.kt index c325409e1a..c9f6399e89 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainViewModel.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainViewModel.kt @@ -16,42 +16,62 @@ package androidx.compose.samples.crane.home +import androidx.compose.samples.crane.calendar.model.DatesSelectedState +import androidx.compose.samples.crane.data.DatesRepository +import androidx.compose.samples.crane.data.DestinationsRepository import androidx.compose.samples.crane.data.ExploreModel -import androidx.compose.samples.crane.data.craneDestinations -import androidx.compose.samples.crane.data.craneHotels -import androidx.compose.samples.crane.data.craneRestaurants +import androidx.compose.samples.crane.di.DefaultDispatcher +import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.random.Random const val MAX_PEOPLE = 4 -class MainViewModel : ViewModel() { +class MainViewModel @ViewModelInject constructor( + private val destinationsRepository: DestinationsRepository, + @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher, + datesRepository: DatesRepository +) : ViewModel() { - val hotels = craneHotels - val restaurants = craneRestaurants + val hotels: List = destinationsRepository.hotels + val restaurants: List = destinationsRepository.restaurants + val datesSelected: DatesSelectedState = datesRepository.datesSelected private val _suggestedDestinations = MutableLiveData>() val suggestedDestinations: LiveData> get() = _suggestedDestinations init { - _suggestedDestinations.value = craneDestinations + _suggestedDestinations.value = destinationsRepository.destinations } fun updatePeople(people: Int) { - if (people > MAX_PEOPLE) { - _suggestedDestinations.value = emptyList() - } else { - // Making Random more random - _suggestedDestinations.value = - craneDestinations.shuffled(Random(people * (1..100).shuffled().first())) + viewModelScope.launch { + if (people > MAX_PEOPLE) { + _suggestedDestinations.value = emptyList() + } else { + val newDestinations = withContext(defaultDispatcher) { + destinationsRepository.destinations + .shuffled(Random(people * (1..100).shuffled().first())) + } + _suggestedDestinations.value = newDestinations + } } } fun toDestinationChanged(newDestination: String) { - _suggestedDestinations.value = - craneDestinations.filter { it.city.nameToDisplay.contains(newDestination) } + viewModelScope.launch { + val newDestinations = withContext(defaultDispatcher) { + destinationsRepository.destinations + .filter { it.city.nameToDisplay.contains(newDestination) } + } + _suggestedDestinations.value = newDestinations + } } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt index 09694334dd..46cffbf6b9 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt @@ -32,7 +32,6 @@ import androidx.compose.runtime.setValue import androidx.compose.samples.crane.R import androidx.compose.samples.crane.base.CraneEditableUserInput import androidx.compose.samples.crane.base.CraneUserInput -import androidx.compose.samples.crane.base.ServiceLocator import androidx.compose.samples.crane.home.PeopleUserInputAnimationState.Invalid import androidx.compose.samples.crane.home.PeopleUserInputAnimationState.Valid import androidx.compose.samples.crane.ui.CraneTheme @@ -114,12 +113,11 @@ fun ToDestinationUserInput(onToDestinationChanged: (String) -> Unit) { } @Composable -fun DatesUserInput(onDateSelectionClicked: () -> Unit) { - val datesSelectedText = ServiceLocator.datesSelected.toString() +fun DatesUserInput(datesSelected: String, onDateSelectionClicked: () -> Unit) { CraneUserInput( modifier = Modifier.clickable(onClick = onDateSelectionClicked), - caption = if (datesSelectedText.isEmpty()) "Select Dates" else null, - text = datesSelectedText, + caption = if (datesSelected.isEmpty()) "Select Dates" else null, + text = datesSelected, vectorImageId = R.drawable.ic_calendar ) } diff --git a/Crane/build.gradle b/Crane/build.gradle index 1f6412ac10..96fe956cf1 100644 --- a/Crane/build.gradle +++ b/Crane/build.gradle @@ -26,11 +26,12 @@ buildscript { dependencies { classpath Libs.androidGradlePlugin classpath Libs.Kotlin.gradlePlugin + classpath Libs.Hilt.gradlePlugin } } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { diff --git a/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt b/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt index b533be766f..b48e95e7c2 100644 --- a/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt +++ b/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt @@ -21,12 +21,12 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val ktLint = "com.pinterest:ktlint:${Versions.ktLint}" const val googleMaps = "com.google.android.libraries.maps:maps:3.1.0-beta" object Accompanist { - private const val version = "0.3.1" + private const val version = "0.3.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -35,12 +35,18 @@ object Libs { const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version" const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version" const val extensions = "org.jetbrains.kotlin:kotlin-android-extensions:$version" + + object Coroutines { + private const val version = "1.4.0-M1" + const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" + const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" + } } object AndroidX { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" const val runtime = "androidx.compose.runtime:runtime:$version" const val runtimeLivedata = "androidx.compose.runtime:runtime-livedata:$version" @@ -51,6 +57,11 @@ object Libs { const val uiTest = "androidx.ui:ui-test:$version" } + object Lifecycle { + private const val version = "2.3.0-beta01" + const val viewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:$version" + } + object UI { const val tooling = "androidx.ui:ui-tooling:${Compose.version}" } @@ -66,10 +77,38 @@ object Libs { const val espressoCore = "androidx.test.espresso:espresso-core:3.2.0" } } + + object Hilt { + private const val version = "2.29.1-alpha" + + const val gradlePlugin = "com.google.dagger:hilt-android-gradle-plugin:$version" + const val android = "com.google.dagger:hilt-android:$version" + const val compiler = "com.google.dagger:hilt-compiler:$version" + const val testing = "com.google.dagger:hilt-android-testing:$version" + + object AndroidX { + private const val version = "1.0.0-alpha02" + + const val compiler = "androidx.hilt:hilt-compiler:$version" + const val viewModel = "androidx.hilt:hilt-lifecycle-viewmodel:$version" + } + } + + object JUnit { + private const val version = "4.13" + const val junit = "junit:junit:$version" + } + + object AssistedInjection { + private const val version = "0.5.2" + + const val dagger = "com.squareup.inject:assisted-inject-annotations-dagger2:$version" + const val processor = "com.squareup.inject:assisted-inject-processor-dagger2:$version" + } } object Urls { const val mavenCentralSnapshotRepo = "https://oss.sonatype.org/content/repositories/snapshots/" - const val composeSnapshotRepo = "https://androidx-dev-prod.appspot.com/snapshots/builds/" + - "${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/" + const val composeSnapshotRepo = "https://androidx.dev/snapshots/builds/" + + "${Libs.AndroidX.Compose.snapshot}/artifacts/repository/" } diff --git a/Crane/gradle.properties b/Crane/gradle.properties index 23339e0df6..338e94c7f3 100644 --- a/Crane/gradle.properties +++ b/Crane/gradle.properties @@ -1,4 +1,21 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. @@ -6,16 +23,20 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true +org.gradle.jvmargs=-Xmx2048m + +# Turn on parallel compilation, caching and on-demand configuration +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.parallel=true + # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX +# Needed for com.google.android.libraries.maps:maps android.enableJetifier=true + # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official diff --git a/Crane/gradle/wrapper/gradle-wrapper.properties b/Crane/gradle/wrapper/gradle-wrapper.properties index fbaa50600d..fd57ef47b9 100644 --- a/Crane/gradle/wrapper/gradle-wrapper.properties +++ b/Crane/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jun 15 11:35:59 CEST 2020 +#Fri Oct 23 09:30:32 CEST 2020 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/JetNews/app/build.gradle b/JetNews/app/build.gradle index f5cbb880de..ae89f0abcd 100644 --- a/JetNews/app/build.gradle +++ b/JetNews/app/build.gradle @@ -90,7 +90,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-beta01" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01" - androidTestImplementation 'junit:junit:4.13' + androidTestImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation "androidx.ui:ui-test:$compose_version" diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt index 3d7f493fef..1cbeecc096 100644 --- a/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt @@ -39,7 +39,7 @@ import org.junit.Test class HomeScreenSnackbarTest { @get:Rule - val composeTestRule = createComposeRule(disableTransitions = true) + val composeTestRule = createComposeRule() @OptIn( ExperimentalMaterialApi::class, diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt index 9340f9dc5b..5aceada18f 100644 --- a/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt @@ -32,7 +32,7 @@ import org.junit.Test class JetnewsUiTest { @get:Rule - val composeTestRule = createComposeRule(disableTransitions = true) + val composeTestRule = createComposeRule() @Before fun setUp() { diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt index f04bdf49c2..110ae4cac7 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt @@ -19,7 +19,6 @@ package com.example.jetnews.ui.article import android.content.Context import android.content.Intent import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -27,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.AlertDialog +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt index 102839619f..605d8fc958 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt @@ -214,7 +214,7 @@ private fun BulletParagraph( Box( modifier = Modifier .preferredSize(8.sp.toDp(), 8.sp.toDp()) - .alignWithSiblings { + .alignBy { // Add an alignment "baseline" 1sp below the bottom of the circle 9.sp.toIntPx() } @@ -224,7 +224,7 @@ private fun BulletParagraph( Text( modifier = Modifier .weight(1f) - .alignWithSiblings(FirstBaseline), + .alignBy(FirstBaseline), text = text, style = textStyle.merge(paragraphStyle) ) diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt index 445a4d02a9..2b63d478f8 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt @@ -16,7 +16,6 @@ package com.example.jetnews.ui.home -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.Text @@ -33,6 +32,7 @@ import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider import androidx.compose.material.DrawerValue import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt index c161c9fb43..f33c643d44 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt @@ -16,16 +16,15 @@ package com.example.jetnews.ui.home -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Icon import androidx.compose.material.IconToggleButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis @@ -131,7 +130,7 @@ fun PostCardHistory(post: Post, navigateTo: (Screen) -> Unit) { ) } ProvideEmphasis(AmbientEmphasisLevels.current.medium) { - Icon(asset = Icons.Filled.MoreVert) + Icon(Icons.Filled.MoreVert) } } } @@ -144,19 +143,13 @@ fun BookmarkButton( ) { IconToggleButton( checked = isBookmarked, - onCheckedChange = { onClick() } + onCheckedChange = { onClick() }, + modifier = modifier ) { - modifier.fillMaxSize() if (isBookmarked) { - Icon( - asset = Icons.Filled.Bookmark, - modifier = modifier - ) + Icon(asset = Icons.Filled.Bookmark) } else { - Icon( - asset = Icons.Filled.BookmarkBorder, - modifier = modifier - ) + Icon(asset = Icons.Filled.BookmarkBorder) } } } diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt index 42f321532c..313c6c59be 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt @@ -16,7 +16,6 @@ package com.example.jetnews.ui.interests -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text @@ -30,6 +29,7 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.material.DrawerValue +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt index fcad3396fe..9916dd5f30 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt @@ -16,11 +16,11 @@ package com.example.jetnews.ui.interests -import androidx.compose.foundation.Icon import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface diff --git a/JetNews/build.gradle b/JetNews/build.gradle index acfb92e0ed..ff6a558e49 100644 --- a/JetNews/build.gradle +++ b/JetNews/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.kotlin_version = '1.4.10' - ext.compose_version = '1.0.0-alpha05' + ext.compose_version = '1.0.0-alpha06' repositories { google() @@ -24,13 +24,13 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0-alpha13' + classpath 'com.android.tools.build:gradle:4.2.0-alpha15' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { @@ -46,7 +46,7 @@ subprojects { targetExclude("$buildDir/**/*.kt") targetExclude('bin/**/*.kt') - ktlint("0.38.1") + ktlint("0.39.0") licenseHeaderFile rootProject.file('spotless/copyright.kt') } } diff --git a/JetNews/gradle.properties b/JetNews/gradle.properties index 099d6a8fe5..9299bc6d0f 100644 --- a/JetNews/gradle.properties +++ b/JetNews/gradle.properties @@ -1,10 +1,31 @@ -## For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m +# Turn on parallel compilation, caching and on-demand configuration org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true @@ -16,4 +37,3 @@ android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" diff --git a/JetNews/gradle/wrapper/gradle-wrapper.properties b/JetNews/gradle/wrapper/gradle-wrapper.properties index 715d777bf2..0136562534 100644 --- a/JetNews/gradle/wrapper/gradle-wrapper.properties +++ b/JetNews/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Sep 16 10:20:00 PDT 2020 +#Tue Oct 27 16:21:59 PDT 2020 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index 2d5968fa24..9fc5610c9f 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -16,7 +16,6 @@ package com.example.jetcaster.ui.home -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.background @@ -35,6 +34,7 @@ import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index 7b110f634d..be277cb0db 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -17,7 +17,6 @@ package com.example.jetcaster.ui.home.category import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.clickable @@ -38,6 +37,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRowForIndexed import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt index 103e51ca33..7de9a3843d 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt @@ -18,10 +18,10 @@ package com.example.jetcaster.util import androidx.compose.animation.animate import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.Icon import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons diff --git a/Jetcaster/build.gradle b/Jetcaster/build.gradle index f3bb7bd530..08cef649e9 100644 --- a/Jetcaster/build.gradle +++ b/Jetcaster/build.gradle @@ -24,17 +24,13 @@ buildscript { } dependencies { - // Downgrade R8 due to b/169095082 - // TODO remove this - classpath 'com.android.tools:r8:2.1.66' - classpath Libs.androidGradlePlugin classpath Libs.Kotlin.gradlePlugin } } plugins { - id 'com.diffplug.spotless' version '5.5.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { diff --git a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt index 1d7000df8c..dc674ad645 100644 --- a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt +++ b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -29,7 +29,7 @@ object Libs { const val material = "com.google.android.material:material:1.1.0" object Accompanist { - private const val version = "0.3.1" + private const val version = "0.3.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -57,16 +57,16 @@ object Libs { const val appcompat = "androidx.appcompat:appcompat:1.2.0-rc01" const val palette = "androidx.palette:palette:1.0.0" - const val core = "androidx.core:core:1.5.0-alpha02" - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha02" + const val core = "androidx.core:core:1.5.0-alpha04" + const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" object Compose { private const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" @get:JvmStatic val snapshotUrl: String - get() = "https://androidx.dev/snapshots/builds/$snapshot/artifacts/ui/repository/" + get() = "https://androidx.dev/snapshots/builds/$snapshot/artifacts/repository/" const val runtime = "androidx.compose.runtime:runtime:$version" const val foundation = "androidx.compose.foundation:foundation:${version}" diff --git a/Jetcaster/gradle.properties b/Jetcaster/gradle.properties index 94c6607b24..646f68d67b 100644 --- a/Jetcaster/gradle.properties +++ b/Jetcaster/gradle.properties @@ -1,14 +1,31 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m +# Turn on parallel compilation, caching and on-demand configurationv org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true @@ -19,4 +36,4 @@ org.gradle.parallel=true android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official diff --git a/Jetcaster/gradle/wrapper/gradle-wrapper.properties b/Jetcaster/gradle/wrapper/gradle-wrapper.properties index 8d8e8abe86..e8894b0281 100644 --- a/Jetcaster/gradle/wrapper/gradle-wrapper.properties +++ b/Jetcaster/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt index 191a3488d9..65ff28d642 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt @@ -46,7 +46,7 @@ import org.junit.Test class ConversationTest { @get:Rule - val composeTestRule = createAndroidComposeRule(disableTransitions = true) + val composeTestRule = createAndroidComposeRule() // Note that keeping these references is only safe if the activity is not recreated. // See: https://issuetracker.google.com/160862278 diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt index e88636be6b..76254051b3 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt @@ -40,7 +40,7 @@ import org.junit.Test class NavigationTest { @get:Rule - val composeTestRule = createAndroidComposeRule(disableTransitions = true) + val composeTestRule = createAndroidComposeRule() // Note that keeping these references is only safe if the activity is not recreated. // See: https://issuetracker.google.com/160862278 diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt index b0367f8bd9..505ae383ae 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt @@ -47,7 +47,7 @@ import org.junit.Test class UserInputTest { @get:Rule - val composeTestRule = createAndroidComposeRule(disableTransitions = true) + val composeTestRule = createAndroidComposeRule() // Note that keeping these references is only safe if the activity is not recreated. // See: https://issuetracker.google.com/160862278 diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt index e0dccf0f72..9e5157e6b2 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt @@ -74,3 +74,11 @@ fun JetchatAppBarPreview() { JetchatAppBar(title = { Text("Preview!") }) } } + +@Preview +@Composable +fun JetchatAppBarPreviewDark() { + JetchatTheme(isDarkTheme = true) { + JetchatAppBar(title = { Text("Preview!") }) + } +} diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt index efbe18eab7..60ef17c249 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt @@ -17,7 +17,6 @@ package com.example.compose.jetchat.conversation import androidx.compose.foundation.ClickableText -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn @@ -43,6 +42,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.LastBaseline import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface @@ -316,7 +316,7 @@ private fun AuthorNameTimestamp(msg: Message) { text = msg.author, style = MaterialTheme.typography.subtitle1, modifier = Modifier - .alignWithSiblings(LastBaseline) + .alignBy(LastBaseline) .relativePaddingFrom(LastBaseline, after = 8.dp) // Space to 1st bubble ) } @@ -325,7 +325,7 @@ private fun AuthorNameTimestamp(msg: Message) { Text( text = msg.timestamp, style = MaterialTheme.typography.caption, - modifier = Modifier.alignWithSiblings(LastBaseline) + modifier = Modifier.alignBy(LastBaseline) ) } } diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt index c1c85f015e..9e124c196a 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt @@ -19,11 +19,11 @@ package com.example.compose.jetchat.conversation import androidx.compose.animation.DpPropKey import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.transition -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.ExtendedFloatingActionButton +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDownward diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt index a0a1dbf93b..ad8646ff7a 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt @@ -24,12 +24,10 @@ import androidx.compose.foundation.AmbientTextStyle import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.Text import androidx.compose.foundation.clickable -import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -49,12 +47,12 @@ import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Button import androidx.compose.material.ButtonConstants import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface import androidx.compose.material.TextButton -import androidx.compose.material.contentColorFor import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AlternateEmail import androidx.compose.material.icons.outlined.Duo @@ -309,27 +307,22 @@ private fun UserInputSelector( } Spacer(modifier = Modifier.weight(1f)) - val backgroundColor = ButtonConstants.defaultButtonBackgroundColor( - enabled = sendMessageEnabled, - disabledColor = MaterialTheme.colors.surface - ) val disabledContentColor = AmbientEmphasisLevels.current.disabled.applyEmphasis(MaterialTheme.colors.onSurface) - val contentColor = ButtonConstants.defaultButtonContentColor( - enabled = sendMessageEnabled, - defaultColor = contentColorFor(backgroundColor), - disabledColor = disabledContentColor + + val buttonColors = ButtonConstants.defaultButtonColors( + disabledBackgroundColor = MaterialTheme.colors.surface, + disabledContentColor = disabledContentColor ) + // Send button Button( modifier = Modifier .padding(horizontal = 16.dp) .preferredHeight(36.dp), - elevation = 0.dp, enabled = sendMessageEnabled, onClick = onMessageSent, - contentColor = contentColor, - backgroundColor = backgroundColor, + colors = buttonColors, border = border, // TODO: Workaround for https://issuetracker.google.com/158830170 contentPadding = PaddingValues(0.dp) @@ -486,25 +479,20 @@ fun ExtendedSelectorInnerButton( selected: Boolean, modifier: Modifier = Modifier ) { - val backgroundColor = if (selected) { - MaterialTheme.colors.onSurface.copy(alpha = 0.08f) - } else { - // Same as background - getSelectorExpandedColor() - } - val color = if (selected) { - MaterialTheme.colors.onSurface - } else { - MaterialTheme.colors.onSurface.copy(alpha = 0.74f) - } + val colors = ButtonConstants.defaultButtonColors( + backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.08f), + disabledBackgroundColor = getSelectorExpandedColor(), // Same as background + contentColor = MaterialTheme.colors.onSurface, + disabledContentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.74f) + ) TextButton( onClick = onClick, modifier = modifier .padding(horizontal = 8.dp, vertical = 8.dp) .preferredHeight(30.dp), shape = MaterialTheme.shapes.medium, - backgroundColor = backgroundColor, - contentColor = color, + enabled = selected, + colors = colors, // TODO: Workaround for https://issuetracker.google.com//158830170 contentPadding = PaddingValues(0.dp) ) { diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt index d2b20a9bd6..67c65bf2d1 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt @@ -16,7 +16,6 @@ package com.example.compose.jetchat.profile -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn @@ -37,6 +36,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Divider import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Color.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Color.kt index c14d855daa..98cbe18a4a 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Color.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Color.kt @@ -16,13 +16,12 @@ package com.example.compose.jetchat.theme +import androidx.compose.material.AmbientElevationOverlay import androidx.compose.material.Colors -import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.Dp -import kotlin.math.ln /** * Return the fully opaque color that results from compositing [onSurface] atop [surface] with the @@ -34,15 +33,12 @@ fun Colors.compositedOnSurface(alpha: Float): Color { } /** - * Elevation overlay logic copied from [Surface] — https://issuetracker.google.com/155181601 + * Calculates the color of an elevated `surface` in dark mode. Returns `surface` in light mode. */ +@Composable fun Colors.elevatedSurface(elevation: Dp): Color { - if (isLight) return surface - val foreground = calculateForeground(elevation) - return foreground.compositeOver(surface) -} - -private fun calculateForeground(elevation: Dp): Color { - val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f - return Color.White.copy(alpha = alpha) + return AmbientElevationOverlay.current?.apply( + color = this.surface, + elevation = elevation + ) ?: this.surface } diff --git a/Jetchat/build.gradle b/Jetchat/build.gradle index 64522a765b..b487e2c1e0 100644 --- a/Jetchat/build.gradle +++ b/Jetchat/build.gradle @@ -32,7 +32,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { diff --git a/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt b/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt index 684053d3d8..6f71928609 100644 --- a/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt +++ b/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -48,7 +48,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" const val foundation = "androidx.compose.foundation:foundation:$version" const val layout = "androidx.compose.foundation:foundation-layout:$version" @@ -90,6 +90,6 @@ object Libs { } object Urls { - const val composeSnapshotRepo = "https://androidx-dev-prod.appspot.com/snapshots/builds/" + - "${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/" + const val composeSnapshotRepo = "https://androidx.dev/snapshots/builds/" + + "${Libs.AndroidX.Compose.snapshot}/artifacts/repository/" } diff --git a/Jetchat/gradle.properties b/Jetchat/gradle.properties index 28624fcf4a..9299bc6d0f 100644 --- a/Jetchat/gradle.properties +++ b/Jetchat/gradle.properties @@ -19,26 +19,21 @@ # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# Turn on parallel compilation, caching and on-demand configuration org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK +# Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true + # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official diff --git a/Jetchat/gradle/wrapper/gradle-wrapper.properties b/Jetchat/gradle/wrapper/gradle-wrapper.properties index 1737975991..436aaff960 100644 --- a/Jetchat/gradle/wrapper/gradle-wrapper.properties +++ b/Jetchat/gradle/wrapper/gradle-wrapper.properties @@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt index a7d0d90aaf..f0fb1817e5 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt @@ -35,47 +35,54 @@ object SnackRepo { fun getSnacks(): List = snackCollections fun getSnack(snackId: Long) = snacks.find { it.id == snackId }!! fun getRelated(@Suppress("UNUSED_PARAMETER") snackId: Long) = related + fun getInspiredByCart() = inspiredByCart fun getFilters() = filters + fun getCart() = cart } /** * Static data */ -val tastyTreats = SnackCollection( +private val tastyTreats = SnackCollection( id = 1L, name = "Android's picks", type = CollectionType.Highlight, snacks = snacks.subList(0, 13) ) -val popular = SnackCollection( +private val popular = SnackCollection( id = 2L, name = "Popular on Jetsnack", snacks = snacks.subList(14, 19) ) -val wfhFavs = tastyTreats.copy( +private val wfhFavs = tastyTreats.copy( id = 3L, name = "WFH favourites" ) -val newlyAdded = popular.copy( +private val newlyAdded = popular.copy( id = 4L, name = "Newly Added" ) -val exclusive = tastyTreats.copy( +private val exclusive = tastyTreats.copy( id = 5L, name = "Only on Jetsnack" ) -val also = tastyTreats.copy( +private val also = tastyTreats.copy( id = 6L, name = "Customers also bought" ) -val snackCollections = listOf( +private val inspiredByCart = tastyTreats.copy( + id = 7L, + name = "Inspired by your cart" +) + +private val snackCollections = listOf( tastyTreats, popular, wfhFavs, @@ -83,7 +90,9 @@ val snackCollections = listOf( exclusive ) -val related = listOf( +private val related = listOf( also, popular ) + +private val cart = snacks.subList(4, 7) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt index bde3ed3503..1dd037692b 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt @@ -17,7 +17,6 @@ package com.example.jetsnack.ui.components import androidx.compose.animation.animate -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box @@ -28,6 +27,7 @@ import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt index 7952b11817..cd499873c0 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt @@ -16,7 +16,7 @@ package com.example.jetsnack.ui.components -import androidx.compose.foundation.Icon +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -29,11 +29,12 @@ import com.example.jetsnack.ui.theme.JetsnackTheme fun JetsnackGradientTintedIconButton( asset: VectorAsset, onClick: () -> Unit, + modifier: Modifier = Modifier, colors: List = JetsnackTheme.colors.interactiveSecondary ) { // This should use a layer + srcIn but needs investigation val blendMode = if (JetsnackTheme.colors.isDark) BlendMode.Darken else BlendMode.Plus - IconButton(onClick = onClick) { + IconButton(onClick = onClick, modifier) { Icon( asset = asset, modifier = Modifier.diagonalGradientTint( diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt new file mode 100644 index 0000000000..7a89a74919 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetsnack.ui.components + +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.ChainStyle +import androidx.compose.foundation.layout.ConstraintLayout +import androidx.compose.foundation.layout.preferredWidthIn +import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AddCircleOutline +import androidx.compose.material.icons.outlined.RemoveCircleOutline +import androidx.compose.runtime.Composable +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.compose.ui.unit.sp +import androidx.ui.tooling.preview.Preview +import com.example.jetsnack.R +import com.example.jetsnack.ui.theme.JetsnackTheme + +@Composable +fun QuantitySelector( + count: Int, + decreaseItemCount: () -> Unit, + increaseItemCount: () -> Unit, + modifier: Modifier = Modifier +) { + ConstraintLayout(modifier = modifier) { + val (qty, minus, quantity, plus) = createRefs() + createHorizontalChain(qty, minus, quantity, plus, chainStyle = ChainStyle.Packed) + ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Text( + text = stringResource(R.string.quantity), + style = MaterialTheme.typography.subtitle1, + color = JetsnackTheme.colors.textSecondary, + modifier = Modifier.constrainAs(qty) { + start.linkTo(parent.start) + linkTo(top = parent.top, bottom = parent.bottom) + } + ) + } + JetsnackGradientTintedIconButton( + asset = Icons.Outlined.RemoveCircleOutline, + onClick = decreaseItemCount, + modifier = Modifier.constrainAs(minus) { + centerVerticallyTo(quantity) + linkTo(top = parent.top, bottom = parent.bottom) + } + ) + ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { + Text( + text = "$count", + style = MaterialTheme.typography.subtitle2, + fontSize = 18.sp, + color = JetsnackTheme.colors.textPrimary, + textAlign = TextAlign.Center, + modifier = Modifier.preferredWidthIn(min = 24.dp).constrainAs(quantity) { + baseline.linkTo(qty.baseline) + } + ) + } + JetsnackGradientTintedIconButton( + asset = Icons.Outlined.AddCircleOutline, + onClick = increaseItemCount, + modifier = Modifier.constrainAs(plus) { + end.linkTo(parent.end) + centerVerticallyTo(quantity) + linkTo(top = parent.top, bottom = parent.bottom) + } + ) + } +} + +@Preview +@Composable +fun QuantitySelectorPreview() { + JetsnackTheme { + JetsnackSurface { + QuantitySelector(1, {}, {}) + } + } +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt index 2878f3db1e..07debb35f1 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.components -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.Text import androidx.compose.foundation.clickable @@ -36,6 +35,7 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyRowFor import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt index 7a13808ada..3827cc7f8a 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt @@ -17,19 +17,329 @@ package com.example.jetsnack.ui.home import androidx.compose.foundation.Text +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ChainStyle +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ConstraintLayout +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.preferredHeight +import androidx.compose.foundation.layout.preferredHeightIn +import androidx.compose.foundation.layout.preferredSize +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.ExperimentalLazyDsl +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.LastBaseline +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.ui.tooling.preview.Preview import com.example.jetsnack.R +import com.example.jetsnack.model.Snack +import com.example.jetsnack.model.SnackCollection +import com.example.jetsnack.model.SnackRepo +import com.example.jetsnack.ui.components.JetsnackButton +import com.example.jetsnack.ui.components.JetsnackDivider +import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.QuantitySelector +import com.example.jetsnack.ui.components.SnackCollection +import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.theme.AlphaNearOpaque +import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.statusBarsHeight @Composable -fun Cart(modifier: Modifier = Modifier) { - Text( - text = stringResource(R.string.home_cart), +fun Cart( + onSnackClick: (Long) -> Unit, + modifier: Modifier = Modifier +) { + val cartSnacks = remember { SnackRepo.getCart() } + val inspiredByCart = remember { SnackRepo.getInspiredByCart() } + Cart(cartSnacks, inspiredByCart, onSnackClick, modifier) +} + +@Composable +fun Cart( + cartSnacks: List, + inspiredByCart: SnackCollection, + onSnackClick: (Long) -> Unit, + modifier: Modifier = Modifier +) { + JetsnackSurface(modifier = modifier.fillMaxSize()) { + Box { + CartContent( + cartSnacks = cartSnacks, + inspiredByCart = inspiredByCart, + onSnackClick = onSnackClick, + modifier = Modifier.align(Alignment.TopCenter) + ) + DestinationBar(modifier = Modifier.align(Alignment.TopCenter)) + CheckoutBar(modifier = Modifier.align(Alignment.BottomCenter)) + } + } +} + +@OptIn(ExperimentalLazyDsl::class) +@Composable +private fun CartContent( + cartSnacks: List, + inspiredByCart: SnackCollection, + onSnackClick: (Long) -> Unit, + modifier: Modifier = Modifier +) { + LazyColumn(modifier) { + item { + Spacer(Modifier.statusBarsHeight(additional = 56.dp)) + Text( + text = stringResource(R.string.cart_order_header, cartSnacks.size), + style = MaterialTheme.typography.h6, + color = JetsnackTheme.colors.brand, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .preferredHeightIn(min = 56.dp) + .padding(horizontal = 24.dp, vertical = 4.dp) + .wrapContentHeight() + ) + } + items(cartSnacks) { cartSnack -> + CartItem(cartSnack, onSnackClick) + } + item { + SummaryItem() + } + item { + SnackCollection( + snackCollection = inspiredByCart, + onSnackClick = onSnackClick, + highlight = false + ) + Spacer(Modifier.height(56.dp)) + } + } +} + +@Composable +fun CartItem( + snack: Snack, + onSnackClick: (Long) -> Unit, + modifier: Modifier = Modifier +) { + val (count, updateCount) = remember { mutableStateOf(1) } + ConstraintLayout( modifier = modifier - .fillMaxSize() - .wrapContentSize() - ) + .fillMaxWidth() + .clickable { onSnackClick(snack.id) } + .padding(horizontal = 24.dp) + ) { + val (divider, image, name, tag, priceSpacer, price, remove, quantity) = createRefs() + createVerticalChain(name, tag, priceSpacer, price, chainStyle = ChainStyle.Packed) + SnackImage( + imageUrl = snack.imageUrl, + modifier = Modifier + .preferredSize(100.dp) + .constrainAs(image) { + top.linkTo(parent.top, margin = 16.dp) + bottom.linkTo(parent.bottom, margin = 16.dp) + start.linkTo(parent.start) + } + ) + Text( + text = snack.name, + style = MaterialTheme.typography.subtitle1, + color = JetsnackTheme.colors.textSecondary, + modifier = Modifier.constrainAs(name) { + linkTo( + start = image.end, + startMargin = 16.dp, + end = remove.start, + endMargin = 16.dp, + bias = 0f + ) + } + ) + IconButton( + onClick = { /* todo */ }, + modifier = Modifier.constrainAs(remove) { + top.linkTo(parent.top) + end.linkTo(parent.end) + }.padding(top = 12.dp) + ) { + Icon(asset = Icons.Filled.Close, tint = JetsnackTheme.colors.brand) + } + Text( + text = snack.tagline, + style = MaterialTheme.typography.body1, + color = JetsnackTheme.colors.textHelp, + modifier = Modifier.constrainAs(tag) { + linkTo( + start = image.end, + startMargin = 16.dp, + end = parent.end, + endMargin = 16.dp, + bias = 0f + ) + } + ) + Spacer( + Modifier + .preferredHeight(8.dp) + .constrainAs(priceSpacer) { + linkTo(top = tag.bottom, bottom = price.top) + } + ) + Text( + text = "$12.99", + style = MaterialTheme.typography.subtitle1, + color = JetsnackTheme.colors.textPrimary, + modifier = Modifier.constrainAs(price) { + linkTo( + start = image.end, + end = quantity.start, + startMargin = 16.dp, + endMargin = 16.dp, + bias = 0f + ) + } + ) + QuantitySelector( + count = count, + decreaseItemCount = { if (count > 0) updateCount(count - 1) }, + increaseItemCount = { updateCount(count + 1) }, + modifier = Modifier.constrainAs(quantity) { + baseline.linkTo(price.baseline) + end.linkTo(parent.end) + } + ) + JetsnackDivider( + Modifier.constrainAs(divider) { + linkTo(start = parent.start, end = parent.end) + top.linkTo(parent.bottom) + } + ) + } +} + +// TODO: Hoist state instead of using hard-coded total price +@Composable +fun SummaryItem(modifier: Modifier = Modifier) { + Column(modifier) { + Text( + text = stringResource(R.string.cart_summary_header), + style = MaterialTheme.typography.h6, + color = JetsnackTheme.colors.brand, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .padding(horizontal = 24.dp) + .preferredHeightIn(min = 56.dp) + .wrapContentHeight() + ) + Row(modifier = Modifier.padding(horizontal = 24.dp)) { + Text( + text = stringResource(R.string.cart_subtotal_label), + style = MaterialTheme.typography.body1, + modifier = Modifier.weight(1f) + .wrapContentWidth(Alignment.Start) + .alignBy(LastBaseline) + ) + Text( + text = "\$27.47", + style = MaterialTheme.typography.body1, + modifier = Modifier.alignBy(LastBaseline) + ) + } + Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { + Text( + text = stringResource(R.string.cart_shipping_label), + style = MaterialTheme.typography.body1, + modifier = Modifier.weight(1f) + .wrapContentWidth(Alignment.Start) + .alignBy(LastBaseline) + ) + Text( + text = "\$3.69", + style = MaterialTheme.typography.body1, + modifier = Modifier.alignBy(LastBaseline) + ) + } + Spacer(modifier = Modifier.preferredHeight(8.dp)) + JetsnackDivider() + Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { + Text( + text = stringResource(R.string.cart_total_label), + style = MaterialTheme.typography.body1, + modifier = Modifier + .weight(1f) + .padding(end = 16.dp) + .wrapContentWidth(Alignment.End) + .alignBy(LastBaseline) + ) + Text( + text = "\$31.16", + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.alignBy(LastBaseline) + ) + } + JetsnackDivider() + } +} + +@Composable +private fun CheckoutBar(modifier: Modifier = Modifier) { + Column( + modifier.background( + JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque) + ) + ) { + JetsnackDivider() + Row { + Spacer(Modifier.weight(1f)) + JetsnackButton( + onClick = { /* todo */ }, + shape = RectangleShape, + modifier = Modifier + .padding(horizontal = 12.dp, vertical = 8.dp) + .weight(1f) + ) { + Text( + text = stringResource(id = R.string.cart_checkout) + ) + } + } + } +} + +@Preview("Cart") +@Composable +fun CartPreview() { + JetsnackTheme { + Cart(onSnackClick = { }) + } +} + +@Preview("Cart • Dark Theme") +@Composable +fun CartDarkPreview() { + JetsnackTheme(darkTheme = true) { + Cart(onSnackClick = { }) + } } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt new file mode 100644 index 0000000000..eeafa68b34 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetsnack.ui.home + +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ExpandMore +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.example.jetsnack.ui.components.JetsnackDivider +import com.example.jetsnack.ui.theme.AlphaNearOpaque +import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.statusBarsPadding + +@Composable +fun DestinationBar(modifier: Modifier = Modifier) { + Column(modifier = modifier.statusBarsPadding()) { + TopAppBar( + backgroundColor = JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque), + contentColor = JetsnackTheme.colors.textSecondary, + elevation = 0.dp + ) { + Text( + text = "Delivery to 1600 Amphitheater Way", + style = MaterialTheme.typography.subtitle1, + color = JetsnackTheme.colors.textSecondary, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) + IconButton( + onClick = { /* todo */ }, + modifier = Modifier.align(Alignment.CenterVertically) + ) { + Icon( + asset = Icons.Outlined.ExpandMore, + tint = JetsnackTheme.colors.brand + ) + } + } + JetsnackDivider() + } +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt index 13f5865b0d..2b83ea5dd1 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt @@ -16,25 +16,14 @@ package com.example.jetsnack.ui.home -import androidx.compose.foundation.Icon -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.TopAppBar -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ExpandMore import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview import com.example.jetsnack.model.Filter @@ -44,10 +33,8 @@ import com.example.jetsnack.ui.components.FilterBar import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.SnackCollection -import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.utils.statusBarsHeight -import com.example.jetsnack.ui.utils.statusBarsPadding @Composable fun Feed( @@ -105,39 +92,6 @@ private fun SnackCollectionList( } } -@Composable -private fun DestinationBar(modifier: Modifier = Modifier) { - Column(modifier = modifier.statusBarsPadding()) { - TopAppBar( - backgroundColor = JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque), - contentColor = JetsnackTheme.colors.textSecondary, - elevation = 0.dp - ) { - Text( - text = "Delivery to 1600 Amphitheater Way", - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically) - ) - IconButton( - onClick = { /* todo */ }, - modifier = Modifier.align(Alignment.CenterVertically) - ) { - Icon( - asset = Icons.Outlined.ExpandMore, - tint = JetsnackTheme.colors.brand - ) - } - } - JetsnackDivider() - } -} - @Preview("Home") @Composable fun HomePreview() { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt index e517c4df46..796a96f877 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt @@ -24,7 +24,6 @@ import androidx.compose.animation.animate import androidx.compose.animation.animatedFloat import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -35,6 +34,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccountCircle @@ -94,7 +94,7 @@ fun Home(onSnackSelected: (Long) -> Unit) { modifier = modifier ) HomeSections.Search -> Search(onSnackSelected, modifier) - HomeSections.Cart -> Cart(modifier) + HomeSections.Cart -> Cart(onSnackSelected, modifier) HomeSections.Profile -> Profile(modifier) } } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt index 0254320b27..e0d650d780 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.home.search -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.clickable @@ -33,6 +32,7 @@ import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumnForIndexed import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt index 970e8ef761..0b1f883612 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt @@ -18,7 +18,6 @@ package com.example.jetsnack.ui.home.search import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Box @@ -34,6 +33,7 @@ import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt index 5f5c84d649..b9fe76cb5f 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.snackdetail -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text @@ -33,15 +32,13 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.layout.preferredWidthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.AddCircleOutline import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material.icons.outlined.RemoveCircleOutline import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -52,7 +49,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.drawLayer import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp @@ -65,8 +61,8 @@ import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.JetsnackButton import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackGradientTintedIconButton import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.QuantitySelector import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.horizontalGradientBackground @@ -315,26 +311,10 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { .then(HzPadding) .preferredHeightIn(min = BottomBarHeight) ) { - Text( - text = stringResource(R.string.quantity), - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary - ) - JetsnackGradientTintedIconButton( - asset = Icons.Outlined.RemoveCircleOutline, - onClick = { if (count > 0) updateCount(count - 1) } - ) - Text( - text = "$count", - style = MaterialTheme.typography.subtitle2, - fontSize = 18.sp, - color = JetsnackTheme.colors.textPrimary, - textAlign = TextAlign.Center, - modifier = Modifier.preferredWidthIn(min = 24.dp) - ) - JetsnackGradientTintedIconButton( - asset = Icons.Outlined.AddCircleOutline, - onClick = { updateCount(count + 1) } + QuantitySelector( + count = count, + decreaseItemCount = { if (count > 0) updateCount(count - 1) }, + increaseItemCount = { updateCount(count + 1) } ) Spacer(Modifier.preferredWidth(16.dp)) JetsnackButton( diff --git a/Jetsnack/app/src/main/res/values/strings.xml b/Jetsnack/app/src/main/res/values/strings.xml index acf19e862e..0186a5b24a 100644 --- a/Jetsnack/app/src/main/res/values/strings.xml +++ b/Jetsnack/app/src/main/res/values/strings.xml @@ -34,4 +34,12 @@ Vanilla, Almond Flour, Eggs, Butter, Cream, Sugar Qty ADD TO CART + + + Order (%1d items) + Summary + Subtotal + Shipping & Handling + Total + Checkout diff --git a/Jetsnack/build.gradle b/Jetsnack/build.gradle index f5b5cd6f48..da3492a695 100644 --- a/Jetsnack/build.gradle +++ b/Jetsnack/build.gradle @@ -32,7 +32,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.6.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { @@ -42,7 +42,7 @@ subprojects { if (!Libs.AndroidX.Compose.snapshot.isEmpty()) { maven { - url "https://androidx.dev/snapshots/builds/${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/" + url "https://androidx.dev/snapshots/builds/${Libs.AndroidX.Compose.snapshot}/artifacts/repository/" } } diff --git a/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt b/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt index 1c0c9884cb..fd6824b3ea 100644 --- a/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt +++ b/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt @@ -21,11 +21,11 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.3.1" + private const val version = "0.3.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -44,11 +44,11 @@ object Libs { } object AndroidX { - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha02" + const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" const val runtime = "androidx.compose.runtime:runtime:$version" const val foundation = "androidx.compose.foundation:foundation:${version}" diff --git a/Jetsnack/gradle.properties b/Jetsnack/gradle.properties index 2ec1b001e4..9299bc6d0f 100644 --- a/Jetsnack/gradle.properties +++ b/Jetsnack/gradle.properties @@ -1,14 +1,31 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m +# Turn on parallel compilation, caching and on-demand configuration org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true diff --git a/Jetsnack/gradle/wrapper/gradle-wrapper.properties b/Jetsnack/gradle/wrapper/gradle-wrapper.properties index 12d38de6a4..be52383ef4 100644 --- a/Jetsnack/gradle/wrapper/gradle-wrapper.properties +++ b/Jetsnack/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/EmailState.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/EmailState.kt index 1ebe11a9a1..0933c77f20 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/EmailState.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/EmailState.kt @@ -28,7 +28,7 @@ class EmailState : * Returns an error to be displayed or null if no error was found */ private fun emailValidationError(email: String): String { - return "Invalid email" + return "Invalid email: $email" } private fun isEmailValid(email: String): Boolean { diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/PasswordState.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/PasswordState.kt index 3aaeb6a97b..4b1ffe32db 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/PasswordState.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/PasswordState.kt @@ -40,6 +40,7 @@ private fun isPasswordValid(password: String): Boolean { return password.length > 3 } +@Suppress("UNUSED_PARAMETER") private fun passwordValidationError(password: String): String { return "Invalid password" } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt index 4f3761b0d8..1c395ad7f5 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt @@ -16,7 +16,6 @@ package com.example.compose.jetsurvey.signinsignup -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -165,10 +164,7 @@ fun ErrorSnackbar( }, action = { data.actionLabel?.let { - TextButton( - onClick = onDismiss, - contentColor = AmbientContentColor.current - ) { + TextButton(onClick = onDismiss) { Text( text = stringResource(id = R.string.dismiss), color = MaterialTheme.colors.snackbarAction diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInSignUp.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInSignUp.kt index 1209e2a7b9..6ecd41c1cd 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInSignUp.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInSignUp.kt @@ -17,7 +17,6 @@ package com.example.compose.jetsurvey.signinsignup import androidx.compose.foundation.AmbientTextStyle -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box @@ -31,6 +30,7 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/UserRepository.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/UserRepository.kt index ef8a9c9610..7401dc4dd0 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/UserRepository.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/UserRepository.kt @@ -37,10 +37,12 @@ object UserRepository { val user: User get() = _user + @Suppress("UNUSED_PARAMETER") fun signIn(email: String, password: String) { _user = User.LoggedInUser(email) } + @Suppress("UNUSED_PARAMETER") fun signUp(email: String, password: String) { _user = User.LoggedInUser(email) } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt index d1808fe780..7046f59797 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt @@ -59,8 +59,7 @@ class SurveyFragment : Fragment() { ) is SurveyState.Result -> SurveyResultScreen( result = surveyState, - onDonePressed = { activity?.onBackPressedDispatcher?.onBackPressed() }, - onBackPressed = { activity?.onBackPressedDispatcher?.onBackPressed() } + onDonePressed = { activity?.onBackPressedDispatcher?.onBackPressed() } ) } } @@ -87,11 +86,13 @@ class SurveyFragment : Fragment() { } } + @Suppress("UNUSED_PARAMETER") private fun takeAPhoto(questionId: Int) { - // unsupported for now + // TODO: unsupported for now } + @Suppress("UNUSED_PARAMETER") private fun selectContact(questionId: Int) { - // unsupported for now + // TODO: unsupported for now } } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt index 089230a765..8d687f0a6a 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt @@ -18,6 +18,7 @@ package com.example.compose.jetsurvey.survey import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -27,9 +28,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.selection.selectable +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Button import androidx.compose.material.Checkbox +import androidx.compose.material.CheckboxConstants import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideEmphasis import androidx.compose.material.RadioButton import androidx.compose.material.RadioButtonConstants import androidx.compose.material.Slider @@ -42,7 +46,30 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.ui.tooling.preview.Preview import com.example.compose.jetsurvey.R +import com.example.compose.jetsurvey.theme.JetsurveyTheme +import com.example.compose.jetsurvey.theme.questionBackground + +@Preview +@Composable +fun QuestionPreview() { + val question = Question( + id = 2, + questionText = R.string.pick_superhero, + answer = PossibleAnswer.SingleChoice( + optionsStringRes = listOf( + R.string.spiderman, + R.string.ironman, + R.string.unikitty, + R.string.captain_planet + ) + ) + ) + JetsurveyTheme { + Question(question = question, answer = null, onAnswer = {}, onAction = { _, _ -> }) + } +} @Composable fun Question( @@ -57,11 +84,20 @@ fun Question( contentPadding = PaddingValues(start = 20.dp, end = 20.dp) ) { Spacer(modifier = Modifier.preferredHeight(44.dp)) - Text( - text = stringResource(id = question.questionText), - style = MaterialTheme.typography.h4, - modifier = Modifier.fillMaxWidth() - ) + Row( + modifier = Modifier.fillMaxWidth().background( + color = MaterialTheme.colors.questionBackground, + shape = MaterialTheme.shapes.small + ) + ) { + ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { + Text( + text = stringResource(id = question.questionText), + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 16.dp) + ) + } + } Spacer(modifier = Modifier.preferredHeight(24.dp)) when (question.answer) { is PossibleAnswer.SingleChoice -> SingleChoiceQuestion( @@ -141,9 +177,7 @@ private fun SingleChoiceQuestion( RadioButton( selected = optionSelected, onClick = onClickHandle, - color = RadioButtonConstants.animateDefaultColor( - selected = optionSelected, - enabled = true, + colors = RadioButtonConstants.defaultColors( selectedColor = MaterialTheme.colors.primary ) ) @@ -188,7 +222,9 @@ private fun MultipleChoiceQuestion( checkedState = selected onAnswerSelected(option.value, selected) }, - checkedColor = MaterialTheme.colors.primary + colors = CheckboxConstants.defaultColors( + checkedColor = MaterialTheme.colors.primary + ) ) Text( text = option.key, diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt index ce294c07bc..3fb7968878 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt @@ -87,6 +87,7 @@ object SurveyRepository { suspend fun getSurvey() = jetpackSurvey + @Suppress("UNUSED_PARAMETER") fun getSurveyResult(answers: List>): SurveyResult { return SurveyResult( library = "Compose", diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt index 9f9a2831f1..a524e76304 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt @@ -16,41 +16,39 @@ package com.example.compose.jetsurvey.survey -import androidx.annotation.StringRes -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.ConstraintLayout +import androidx.compose.foundation.layout.ExperimentalLayout 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.preferredHeight -import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.Button +import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton +import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.material.TextButton -import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ChevronLeft -import androidx.compose.material.icons.filled.FiberManualRecord -import androidx.compose.material.icons.outlined.FiberManualRecord +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.savedinstancestate.savedInstanceState import androidx.compose.runtime.setValue -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 com.example.compose.jetsurvey.R +import com.example.compose.jetsurvey.theme.progressIndicatorBackground @Composable fun SurveyQuestionsScreen( @@ -65,12 +63,21 @@ fun SurveyQuestionsScreen( Surface(modifier = Modifier.fillMaxSize()) { Scaffold( - topBar = { SurveyTopAppBar(questions.surveyTitle, onBackPressed) }, + topBar = { + SurveyTopAppBar( + questionIndex = questionState.questionIndex, + totalQuestionsCount = questionState.totalQuestionsCount, + onBackPressed = onBackPressed + ) + }, bodyContent = { innerPadding -> Question( question = questionState.question, answer = questionState.answer, - onAnswer = { questionState.answer = it }, + onAnswer = { + questionState.answer = it + questionState.enableNext = true + }, onAction = onAction, modifier = Modifier .fillMaxSize() @@ -92,12 +99,10 @@ fun SurveyQuestionsScreen( @Composable fun SurveyResultScreen( result: SurveyState.Result, - onDonePressed: () -> Unit, - onBackPressed: () -> Unit + onDonePressed: () -> Unit ) { Surface(modifier = Modifier.fillMaxSize()) { Scaffold( - topBar = { SurveyTopAppBar(result.surveyTitle, onBackPressed) }, bodyContent = { innerPadding -> val modifier = Modifier.padding(innerPadding) SurveyResult(result = result, modifier = modifier) @@ -139,31 +144,46 @@ private fun SurveyResult(result: SurveyState.Result, modifier: Modifier = Modifi } } +@OptIn(ExperimentalLayout::class) @Composable private fun SurveyTopAppBar( - @StringRes surveyTitle: Int, + questionIndex: Int, + totalQuestionsCount: Int, onBackPressed: () -> Unit ) { - TopAppBar( - title = { - Text( - text = stringResource(id = surveyTitle), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.Filled.ChevronLeft) + ConstraintLayout(modifier = Modifier.fillMaxWidth()) { + val (button, text, progress) = createRefs() + Text( + text = stringResource( + R.string.question_count, + questionIndex + 1, + totalQuestionsCount + ), + style = MaterialTheme.typography.caption, + modifier = Modifier.padding(vertical = 20.dp).constrainAs(text) { + centerHorizontallyTo(parent) + } + ) + + ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + IconButton( + onClick = onBackPressed, + modifier = Modifier.padding(horizontal = 12.dp).constrainAs(button) { + end.linkTo(parent.end) + } + ) { + Icon(Icons.Filled.Close) } - }, - // We need to balance the navigation icon, so we add a spacer. - actions = { - Spacer(modifier = Modifier.preferredWidth(68.dp)) - }, - backgroundColor = MaterialTheme.colors.surface, - elevation = 0.dp - ) + } + + LinearProgressIndicator( + progress = (questionIndex + 1) / totalQuestionsCount.toFloat(), + modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp).constrainAs(progress) { + bottom.linkTo(text.bottom) + }, + backgroundColor = MaterialTheme.colors.progressIndicatorBackground + ) + } } @Composable @@ -173,53 +193,41 @@ private fun SurveyBottomBar( onNextPressed: () -> Unit, onDonePressed: () -> Unit ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 20.dp, end = 20.dp, bottom = 24.dp) + Surface( + elevation = 3.dp, + modifier = Modifier.fillMaxWidth() ) { - TextButton( - modifier = Modifier.weight(1f).wrapContentWidth(align = Alignment.Start), - onClick = onPreviousPressed, - enabled = questionState.enablePrevious + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 20.dp) ) { - Text(text = stringResource(id = R.string.previous)) - } - PageIndicator( - pagesCount = questionState.totalQuestionsCount, - currentPageIndex = questionState.questionIndex - ) - if (questionState.showDone) { - TextButton( - modifier = Modifier.weight(1f).wrapContentWidth(align = Alignment.End), - onClick = onDonePressed - ) { - Text(text = stringResource(id = R.string.done)) - } - } else { - TextButton( - modifier = Modifier.weight(1f).wrapContentWidth(align = Alignment.End), - onClick = onNextPressed - ) { - Text(text = stringResource(id = R.string.next)) + if (questionState.showPrevious) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onPreviousPressed + ) { + Text(text = stringResource(id = R.string.previous)) + } + Spacer(modifier = Modifier.width(16.dp)) } - } - } -} - -@Composable -private fun PageIndicator(pagesCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) { - Row(modifier = modifier.wrapContentSize(align = Alignment.Center)) { - for (pageIndex in 0 until pagesCount) { - val asset = if (currentPageIndex == pageIndex) { - Icons.Filled.FiberManualRecord + if (questionState.showDone) { + Button( + modifier = Modifier.weight(1f), + onClick = onDonePressed, + enabled = questionState.enableNext + ) { + Text(text = stringResource(id = R.string.done)) + } } else { - Icons.Outlined.FiberManualRecord + Button( + modifier = Modifier.weight(1f), + onClick = onNextPressed, + enabled = questionState.enableNext + ) { + Text(text = stringResource(id = R.string.next)) + } } - Icon( - asset = asset, - tint = MaterialTheme.colors.primary - ) } } } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt index 8267a41bca..c457901ccb 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt @@ -27,9 +27,10 @@ class QuestionState( val question: Question, val questionIndex: Int, val totalQuestionsCount: Int, - val enablePrevious: Boolean, + val showPrevious: Boolean, val showDone: Boolean ) { + var enableNext by mutableStateOf(false) var answer by mutableStateOf?>(null) } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt index 64928ab636..944c32dacc 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt @@ -37,9 +37,15 @@ class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewMode // Create the default questions state based on the survey questions val questions: List = survey.questions.mapIndexed { index, question -> - val enablePrevious = index > 0 + val showPrevious = index > 0 val showDone = index == survey.questions.size - 1 - QuestionState(question, index, survey.questions.size, enablePrevious, showDone) + QuestionState( + question = question, + questionIndex = index, + totalQuestionsCount = survey.questions.size, + showPrevious = showPrevious, + showDone = showDone + ) } surveyInitialState = SurveyState.Questions(survey.title, questions) _uiState.value = surveyInitialState @@ -64,6 +70,7 @@ class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewMode questionState.question.id == questionId } question.answer = Answer.Action(result) + question.enableNext = true } } } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Color.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Color.kt index 5d82e95450..5efe51c9c8 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Color.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Color.kt @@ -25,3 +25,6 @@ val Purple800 = Color(0xFF0000E1) val Red300 = Color(0xFFD00036) val Red800 = Color(0xFFEA6D7E) + +val Gray100 = Color(0xFFF5F5F5) +val Gray900 = Color(0xFF212121) diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt index fbf84bda35..748aa869e8 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt @@ -56,6 +56,14 @@ val DarkThemeColors = darkColors( val Colors.snackbarAction: Color get() = if (isLight) Purple300 else Purple700 +@Composable +val Colors.progressIndicatorBackground: Color + get() = if (isLight) Color.Black.copy(alpha = 0.12f) else Color.Black.copy(alpha = 0.24f) + +@Composable +val Colors.questionBackground: Color + get() = if (isLight) Gray100 else Gray900 + @Composable fun JetsurveyTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { val colors = if (darkTheme) { diff --git a/Jetsurvey/app/src/main/res/values/strings.xml b/Jetsurvey/app/src/main/res/values/strings.xml index db80f73f23..368cc33b99 100644 --- a/Jetsurvey/app/src/main/res/values/strings.xml +++ b/Jetsurvey/app/src/main/res/values/strings.xml @@ -21,22 +21,24 @@ Password Confirm password Sign in - Sign in as guest + SIGN IN + SIGN IN AS GUEST Sign in or create an account or - Forgot password - Continue - Create account + FORGOT PASSWORD? + CONTINUE + CREATE ACCOUNT By continuing, you agree to our Terms of Service. We’ll handle your data according to our Privacy Policy. Feature not available - Dismiss - Next - Previous - Done + DISMISS + NEXT + PREVIOUS + DONE Which Jetpack library are you? + %1$d of %2$d In my free time I like to … diff --git a/Jetsurvey/build.gradle b/Jetsurvey/build.gradle index 8963493a05..21f2f12519 100644 --- a/Jetsurvey/build.gradle +++ b/Jetsurvey/build.gradle @@ -29,7 +29,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { @@ -57,6 +57,9 @@ subprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { + // Treat all Kotlin warnings as errors + allWarningsAsErrors = true + freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' freeCompilerArgs += '-Xallow-jvm-ir-dependencies' diff --git a/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt b/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt index 9e08760240..d7ae38de39 100644 --- a/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt +++ b/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt @@ -17,11 +17,11 @@ package com.example.compose.jetsurvey.buildsrc object Versions { - const val ktlint = "0.38.1" + const val ktlint = "0.39.0" } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -41,11 +41,11 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" @get:JvmStatic val snapshotUrl: String - get() = "https://androidx.dev/snapshots/builds/$snapshot/artifacts/ui/repository/" + get() = "https://androidx.dev/snapshots/builds/$snapshot/artifacts/repository/" const val foundation = "androidx.compose.foundation:foundation:$version" const val layout = "androidx.compose.foundation:foundation-layout:$version" diff --git a/Jetsurvey/gradle.properties b/Jetsurvey/gradle.properties index 9bb1cb21f9..9299bc6d0f 100644 --- a/Jetsurvey/gradle.properties +++ b/Jetsurvey/gradle.properties @@ -1,4 +1,21 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. @@ -7,15 +24,16 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true + +# Turn on parallel compilation, caching and on-demand configuration +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.parallel=true + # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK +# Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true + # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official diff --git a/Jetsurvey/gradle/wrapper/gradle-wrapper.properties b/Jetsurvey/gradle/wrapper/gradle-wrapper.properties index e194248109..9a517b7c1a 100644 --- a/Jetsurvey/gradle/wrapper/gradle-wrapper.properties +++ b/Jetsurvey/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip diff --git a/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt b/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt index 7a74b9ef2f..a3de83fbb6 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt @@ -16,7 +16,6 @@ package com.example.owl.ui.common -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -28,6 +27,7 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.icons.Icons diff --git a/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt b/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt index 43291465b8..1046aa011d 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt @@ -17,7 +17,6 @@ package com.example.owl.ui.course import androidx.compose.animation.animate -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.ScrollableRow @@ -41,6 +40,7 @@ import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt index d1786663e6..e8c92b7eae 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt @@ -19,13 +19,13 @@ package com.example.owl.ui.courses import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt index 0bc6cbffc2..215797d4cc 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt @@ -16,7 +16,6 @@ package com.example.owl.ui.courses -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.clickable @@ -25,6 +24,8 @@ import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize +import androidx.compose.material.AmbientElevationOverlay +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.icons.Icons @@ -41,7 +42,6 @@ import com.example.owl.model.courses import com.example.owl.ui.common.OutlinedAvatar import com.example.owl.ui.theme.BlueTheme import com.example.owl.ui.theme.OwlTheme -import com.example.owl.ui.theme.elevatedSurface import com.example.owl.ui.utils.NetworkImage import com.example.owl.ui.utils.statusBarsPadding import kotlin.math.ceil @@ -93,11 +93,13 @@ fun FeaturedCourse( top.linkTo(parent.top) } ) + val outlineColor = AmbientElevationOverlay.current?.apply( + color = MaterialTheme.colors.surface, + elevation = OwlTheme.elevations.card + ) ?: MaterialTheme.colors.surface OutlinedAvatar( url = course.instructor, - outlineColor = MaterialTheme.colors.elevatedSurface( - OwlTheme.elevations.card - ), + outlineColor = outlineColor, modifier = Modifier .preferredSize(38.dp) .constrainAs(avatar) { diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt index 74d2ea3b40..450edaaa3f 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt @@ -18,7 +18,6 @@ package com.example.owl.ui.courses import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text @@ -26,6 +25,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.TopAppBar diff --git a/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt b/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt index 029b6a30f0..f1e7925754 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.DpPropKey import androidx.compose.animation.core.FloatPropKey import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.transition -import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.horizontalScroll @@ -40,6 +39,7 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CornerSize import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/Owl/app/src/main/java/com/example/owl/ui/theme/Color.kt b/Owl/app/src/main/java/com/example/owl/ui/theme/Color.kt index 7768bb21ec..1d38c957b4 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/theme/Color.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/theme/Color.kt @@ -17,12 +17,9 @@ package com.example.owl.ui.theme import androidx.compose.material.Colors -import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.unit.Dp -import kotlin.math.ln val yellow200 = Color(0xffffeb46) val yellow400 = Color(0xffffc000) @@ -47,17 +44,3 @@ val pinkDarkPrimary = Color(0xff24191c) fun Colors.compositedOnSurface(alpha: Float): Color { return onSurface.copy(alpha = alpha).compositeOver(surface) } - -/** - * Elevation overlay logic copied from [Surface] — https://issuetracker.google.com/155181601 - */ -fun Colors.elevatedSurface(elevation: Dp): Color { - if (isLight) return surface - val foreground = calculateForeground(elevation) - return foreground.compositeOver(surface) -} - -private fun calculateForeground(elevation: Dp): Color { - val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f - return Color.White.copy(alpha = alpha) -} diff --git a/Owl/build.gradle b/Owl/build.gradle index 38e5b90065..48c4a24d02 100644 --- a/Owl/build.gradle +++ b/Owl/build.gradle @@ -29,7 +29,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { @@ -39,7 +39,7 @@ subprojects { if (!Libs.AndroidX.Compose.snapshot.isEmpty()) { maven { - url "https://androidx.dev/snapshots/builds/${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/" + url "https://androidx.dev/snapshots/builds/${Libs.AndroidX.Compose.snapshot}/artifacts/repository/" } } diff --git a/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt b/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt index 5a83c5c5fc..bf9d478d07 100644 --- a/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt +++ b/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt @@ -21,11 +21,11 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.3.1" + private const val version = "0.3.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -44,11 +44,11 @@ object Libs { } object AndroidX { - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha02" + const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" const val runtime = "androidx.compose.runtime:runtime:$version" const val foundation = "androidx.compose.foundation:foundation:$version" diff --git a/Owl/gradle.properties b/Owl/gradle.properties index 2ec1b001e4..9299bc6d0f 100644 --- a/Owl/gradle.properties +++ b/Owl/gradle.properties @@ -1,14 +1,31 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # Project-wide Gradle settings. + # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m +# Turn on parallel compilation, caching and on-demand configuration org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true diff --git a/Owl/gradle/wrapper/gradle-wrapper.jar b/Owl/gradle/wrapper/gradle-wrapper.jar index 62d4c05355..e708b1c023 100644 Binary files a/Owl/gradle/wrapper/gradle-wrapper.jar and b/Owl/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Owl/gradle/wrapper/gradle-wrapper.properties b/Owl/gradle/wrapper/gradle-wrapper.properties index a29c0b9852..be52383ef4 100644 --- a/Owl/gradle/wrapper/gradle-wrapper.properties +++ b/Owl/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Jun 10 18:35:56 BST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip diff --git a/Owl/gradlew b/Owl/gradlew index fbd7c51583..4f906e0c81 100755 --- a/Owl/gradlew +++ b/Owl/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/Owl/gradlew.bat b/Owl/gradlew.bat index a9f778a7a9..ac1b06f938 100644 --- a/Owl/gradlew.bat +++ b/Owl/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt b/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt index 9c0613ba10..9d2d00bac4 100644 --- a/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt +++ b/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt @@ -37,12 +37,14 @@ import org.junit.Test * For assertions, a simple screenshot testing framework is used. It requires SDK 26+ and to * be run on a device with 420dpi, as that the density used to generate the golden images * present in androidTest/assets. It runs bitmap comparisons on device. + * + * Note that different systems can produce slightly different screenshots making the test fail. */ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O) class AnimatingCircleTests { @get:Rule - val composeTestRule = createComposeRule(disableTransitions = false) + val composeTestRule = createComposeRule() @Test fun circleAnimation_idle_screenshot() { diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt index 08823687c1..ea2b6f054c 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt @@ -16,7 +16,6 @@ package com.example.compose.rally.ui.components -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -29,6 +28,7 @@ import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.icons.Icons diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt index ba4c90023e..532fb188f7 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.animate import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween -import androidx.compose.foundation.Icon import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -29,6 +28,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.selection.selectable +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.ripple.RippleIndication diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt index 814ec10866..cb11b7119f 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt @@ -16,7 +16,6 @@ package com.example.compose.rally.ui.overview -import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text import androidx.compose.foundation.background @@ -30,6 +29,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Card +import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis diff --git a/Rally/build.gradle b/Rally/build.gradle index 6ccc84cc18..19f440f706 100644 --- a/Rally/build.gradle +++ b/Rally/build.gradle @@ -31,7 +31,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.1.1' + id 'com.diffplug.spotless' version '5.7.0' } subprojects { diff --git a/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt b/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt index 11a2c38908..39e7f684cb 100644 --- a/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt +++ b/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -48,7 +48,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha05" + const val version = "1.0.0-alpha06" const val core = "androidx.compose.ui:ui:$version" const val foundation = "androidx.compose.foundation:foundation:$version" @@ -92,5 +92,5 @@ object Libs { object Urls { const val composeSnapshotRepo = "https://androidx-dev-prod.appspot.com/snapshots/builds/" + - "${Libs.AndroidX.Compose.snapshot}/artifacts/ui/repository/" + "${Libs.AndroidX.Compose.snapshot}/artifacts/repository/" } diff --git a/Rally/gradle.properties b/Rally/gradle.properties index 28624fcf4a..9299bc6d0f 100644 --- a/Rally/gradle.properties +++ b/Rally/gradle.properties @@ -19,26 +19,21 @@ # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# Turn on parallel compilation, caching and on-demand configuration org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK +# Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true + # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official diff --git a/Rally/gradle/wrapper/gradle-wrapper.properties b/Rally/gradle/wrapper/gradle-wrapper.properties index 1737975991..436aaff960 100644 --- a/Rally/gradle/wrapper/gradle-wrapper.properties +++ b/Rally/gradle/wrapper/gradle-wrapper.properties @@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip