├── ASSETS_LICENSE ├── CONTRIBUTING.md ├── CompleteAppCodelab ├── .gitignore ├── README.md ├── ReflectDesign.fig ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── relay │ │ │ └── example │ │ │ └── reflect │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── google │ │ │ │ └── relay │ │ │ │ └── example │ │ │ │ └── reflect │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── model │ │ │ │ ├── ReflectModel.kt │ │ │ │ ├── ReflectRepo.kt │ │ │ │ ├── Tracker.kt │ │ │ │ └── TrackerData.kt │ │ │ │ └── ui │ │ │ │ ├── HomeScreen.kt │ │ │ │ ├── SettingsScreen.kt │ │ │ │ ├── components │ │ │ │ ├── DayNavigationControl.kt │ │ │ │ ├── EmojiSelector.kt │ │ │ │ ├── Emojis.kt │ │ │ │ ├── RangeControl.kt │ │ │ │ ├── ReflectButton.kt │ │ │ │ ├── ReflectDropdownMenu.kt │ │ │ │ ├── ReflectTextField.kt │ │ │ │ ├── SwitchControl.kt │ │ │ │ ├── TrackerControl.kt │ │ │ │ ├── TrackerSettingsControl.kt │ │ │ │ └── ValueControl.kt │ │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── ic_launcher_monochrome.xml │ │ │ ├── font │ │ │ │ ├── dm_sans_bold.ttf │ │ │ │ ├── dm_sans_medium.ttf │ │ │ │ └── dm_sans_regular.ttf │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ └── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ └── ui-package-resources │ │ │ └── mappings │ │ │ ├── README.md │ │ │ ├── button.json │ │ │ └── dropdown_menu.json │ │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── relay │ │ └── example │ │ └── reflect │ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── LICENSE └── README.md /ASSETS_LICENSE: -------------------------------------------------------------------------------- 1 | All font files are licensed under the SIL OPEN FONT LICENSE license. All other files are licensed under the Apache 2 license. 2 | 3 | 4 | SIL OPEN FONT LICENSE 5 | Version 1.1 - 26 February 2007 6 | 7 | PREAMBLE 8 | The goals of the Open Font License (OFL) are to stimulate worldwide 9 | development of collaborative font projects, to support the font creation 10 | efforts of academic and linguistic communities, and to provide a free and 11 | open framework in which fonts may be shared and improved in partnership 12 | with others. 13 | 14 | The OFL allows the licensed fonts to be used, studied, modified and 15 | redistributed freely as long as they are not sold by themselves. The 16 | fonts, including any derivative works, can be bundled, embedded, 17 | redistributed and/or sold with any software provided that any reserved 18 | names are not used by derivative works. The fonts and derivatives, 19 | however, cannot be released under any other type of license. The 20 | requirement for fonts to remain under this license does not apply 21 | to any document created using the fonts or their derivatives. 22 | 23 | DEFINITIONS 24 | "Font Software" refers to the set of files released by the Copyright 25 | Holder(s) under this license and clearly marked as such. This may 26 | include source files, build scripts and documentation. 27 | 28 | "Reserved Font Name" refers to any names specified as such after the 29 | copyright statement(s). 30 | 31 | "Original Version" refers to the collection of Font Software components as 32 | distributed by the Copyright Holder(s). 33 | 34 | "Modified Version" refers to any derivative made by adding to, deleting, 35 | or substituting — in part or in whole — any of the components of the 36 | Original Version, by changing formats or by porting the Font Software to a 37 | new environment. 38 | 39 | "Author" refers to any designer, engineer, programmer, technical 40 | writer or other person who contributed to the Font Software. 41 | 42 | PERMISSION & CONDITIONS 43 | Permission is hereby granted, free of charge, to any person obtaining 44 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 45 | redistribute, and sell modified and unmodified copies of the Font 46 | Software, subject to the following conditions: 47 | 48 | 1) Neither the Font Software nor any of its individual components, 49 | in Original or Modified Versions, may be sold by itself. 50 | 51 | 2) Original or Modified Versions of the Font Software may be bundled, 52 | redistributed and/or sold with any software, provided that each copy 53 | contains the above copyright notice and this license. These can be 54 | included either as stand-alone text files, human-readable headers or 55 | in the appropriate machine-readable metadata fields within text or 56 | binary files as long as those fields can be easily viewed by the user. 57 | 58 | 3) No Modified Version of the Font Software may use the Reserved Font 59 | Name(s) unless explicit written permission is granted by the corresponding 60 | Copyright Holder. This restriction only applies to the primary font name as 61 | presented to the users. 62 | 63 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 64 | Software shall not be used to promote, endorse or advertise any 65 | Modified Version, except to acknowledge the contribution(s) of the 66 | Copyright Holder(s) and the Author(s) or with their explicit written 67 | permission. 68 | 69 | 5) The Font Software, modified or unmodified, in part or in whole, 70 | must be distributed entirely under this license, and must not be 71 | distributed under any other license. The requirement for fonts to 72 | remain under this license does not apply to any document created 73 | using the Font Software. 74 | 75 | TERMINATION 76 | This license becomes null and void if any of the above conditions are 77 | not met. 78 | 79 | DISCLAIMER 80 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 81 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 82 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 83 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 84 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 85 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 86 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 87 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 88 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to Relay codelabs. At this time, 4 | we are not accepting external contributions to this project. The codelabs are 5 | maintained internally to Google and mirrored to GitHub, in order to keep them 6 | up to date with Relay itself. 7 | 8 | # Reporting Bugs 9 | 10 | Your feedback is still valuable to us! If you encounter any bugs or issues, we 11 | encourage you to report them using our 12 | [bug report form](https://goo.gle/relay-feedback). Your input will help us 13 | identify problems and improve Relay for everyone. 14 | 15 | When submitting bugs, please include as much information as you can, including 16 | steps to reproduce the issue, relevant screenshots or error logs, and details 17 | about the Figma file and Android Studio project in use. 18 | 19 | # Stay Connected 20 | 21 | If you are interested in staying up-to-date with the latest news and updates 22 | about the project, please consider signing up for our 23 | [newsletter](https://services.google.com/fb/forms/relay-updates/) or 24 | joining our [Discord](https://discord.com/invite/d6brKhT3RB). 25 | 26 | Thanks for giving Relay a try! 27 | -------------------------------------------------------------------------------- /CompleteAppCodelab/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /CompleteAppCodelab/README.md: -------------------------------------------------------------------------------- 1 | # Reflect: A Daily Habit Tracker 2 | 3 | This directory contains source code for *Reflect*, an example Android 4 | application written using [Jetpack Compose](https://developer.android.com/jetpack/compose) 5 | and [Relay](https://relay.material.io/). 6 | 7 | Reflect allows users to create personalized data trackers, enter daily values, 8 | and track them over time. This project is part of a codelab that focuses on 9 | crafting the user interface components using Relay, specifically: 10 | 11 | 1. Importing components from Figma as UI packages 12 | 2. Adding interaction and behavior 13 | 3. Integrating application themes 14 | 4. Mapping UI packages to custom components 15 | -------------------------------------------------------------------------------- /CompleteAppCodelab/ReflectDesign.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/ReflectDesign.fig -------------------------------------------------------------------------------- /CompleteAppCodelab/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /CompleteAppCodelab/app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'com.android.application' 19 | id 'org.jetbrains.kotlin.android' 20 | id 'com.google.relay' version '0.3.12' 21 | } 22 | 23 | android { 24 | namespace 'com.google.relay.example.reflect' 25 | compileSdk 33 26 | 27 | defaultConfig { 28 | applicationId "com.google.relay.example.reflect" 29 | minSdk 24 30 | targetSdk 33 31 | versionCode 1 32 | versionName "1.0" 33 | 34 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 35 | vectorDrawables { 36 | useSupportLibrary true 37 | } 38 | } 39 | 40 | buildTypes { 41 | release { 42 | minifyEnabled false 43 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 44 | } 45 | } 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_1_8 48 | targetCompatibility JavaVersion.VERSION_1_8 49 | } 50 | kotlinOptions { 51 | jvmTarget = '1.8' 52 | } 53 | buildFeatures { 54 | compose true 55 | } 56 | composeOptions { 57 | kotlinCompilerExtensionVersion '1.4.0' 58 | } 59 | packagingOptions { 60 | resources { 61 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 62 | } 63 | } 64 | } 65 | 66 | dependencies { 67 | 68 | implementation 'androidx.core:core-ktx:1.7.0' 69 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' 70 | implementation 'androidx.activity:activity-compose:1.3.1' 71 | implementation "androidx.compose.ui:ui:$compose_ui_version" 72 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" 73 | implementation "androidx.compose.material3:material3:$material3_version" 74 | implementation 'androidx.appcompat:appcompat:1.6.1' 75 | 76 | implementation 'com.google.accompanist:accompanist-navigation-animation:0.29.2-rc' 77 | 78 | testImplementation 'junit:junit:4.13.2' 79 | 80 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 81 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 82 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version" 83 | 84 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" 85 | debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" 86 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/androidTest/java/com/google/relay/example/reflect/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect 18 | 19 | import androidx.test.platform.app.InstrumentationRegistry 20 | import androidx.test.ext.junit.runners.AndroidJUnit4 21 | 22 | import org.junit.Test 23 | import org.junit.runner.RunWith 24 | 25 | import org.junit.Assert.* 26 | 27 | /** 28 | * Instrumented test, which will execute on an Android device. 29 | * 30 | * See [testing documentation](http://d.android.com/tools/testing). 31 | */ 32 | @RunWith(AndroidJUnit4::class) 33 | class ExampleInstrumentedTest { 34 | @Test 35 | fun useAppContext() { 36 | // Context of the app under test. 37 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 38 | assertEquals("com.google.relay.example.reflect", appContext.packageName) 39 | } 40 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect 18 | 19 | import android.os.Bundle 20 | import androidx.activity.ComponentActivity 21 | import androidx.activity.compose.setContent 22 | import androidx.compose.animation.AnimatedContentScope 23 | import androidx.compose.animation.ExperimentalAnimationApi 24 | import androidx.compose.animation.core.tween 25 | import androidx.compose.foundation.layout.* 26 | import androidx.compose.material3.* 27 | import androidx.compose.runtime.* 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.tooling.preview.Preview 30 | import androidx.navigation.NavHostController 31 | import androidx.navigation.NavType 32 | import androidx.navigation.navArgument 33 | import com.google.accompanist.navigation.animation.AnimatedNavHost 34 | import com.google.accompanist.navigation.animation.composable 35 | import com.google.accompanist.navigation.animation.rememberAnimatedNavController 36 | import com.google.relay.example.reflect.model.ReflectRepo 37 | import com.google.relay.example.reflect.ui.HomeScreen 38 | import com.google.relay.example.reflect.ui.SettingsScreen 39 | import com.google.relay.example.reflect.ui.theme.ReflectTheme 40 | 41 | class MainActivity : ComponentActivity() { 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | setContent { 45 | ReflectTheme { 46 | // A surface container using the 'background' color from the theme 47 | Surface( 48 | modifier = Modifier.fillMaxSize(), 49 | color = MaterialTheme.colorScheme.background, 50 | ) { 51 | ReflectNavHost() 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | /* 59 | * Host for navigating between main screen and settings screen 60 | */ 61 | @OptIn(ExperimentalAnimationApi::class) 62 | @Composable 63 | fun ReflectNavHost( 64 | modifier: Modifier = Modifier, 65 | navController: NavHostController = rememberAnimatedNavController(), 66 | startDestination: String = "home" 67 | ) { 68 | AnimatedNavHost( 69 | modifier = modifier, 70 | navController = navController, 71 | startDestination = startDestination 72 | ) { 73 | composable( 74 | "home", 75 | ) { 76 | HomeScreen( 77 | onEditTracker = { 78 | val index = ReflectRepo.model.getIndex(it) 79 | navController.navigate("settings/$index") 80 | }, 81 | onNewTracker = { 82 | navController.navigate("settings/-1") 83 | } 84 | ) 85 | } 86 | composable( 87 | "settings/{trackerIndex}", 88 | arguments = listOf(navArgument("trackerIndex") { type = NavType.IntType }), 89 | enterTransition = { 90 | slideIntoContainer( 91 | AnimatedContentScope.SlideDirection.Up, 92 | animationSpec = tween(500) 93 | ) 94 | }, 95 | exitTransition = { 96 | slideOutOfContainer( 97 | AnimatedContentScope.SlideDirection.Down, 98 | animationSpec = tween(500) 99 | ) 100 | }, 101 | ) { 102 | val trackerIndex = it.arguments?.getInt("trackerIndex") 103 | val tracker = if (trackerIndex == null || trackerIndex == -1) 104 | null 105 | else 106 | ReflectRepo.model.getTracker(trackerIndex) 107 | SettingsScreen(tracker) { 108 | navController.navigate("home") 109 | } 110 | } 111 | } 112 | } 113 | 114 | 115 | @Preview(showBackground = true) 116 | @Composable 117 | fun DefaultPreview() { 118 | ReflectTheme { 119 | HomeScreen() 120 | } 121 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/model/ReflectModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.model 18 | 19 | import androidx.compose.runtime.Stable 20 | import androidx.compose.runtime.mutableStateListOf 21 | import androidx.compose.runtime.toMutableStateList 22 | import java.util.* 23 | 24 | /* 25 | * Core data model for tracker information. 26 | * 27 | * ReflectModel ties together three pieces of information: 28 | * - Tracker contains information a user has configured for tracking 29 | * - TrackerData contains the specific value for a tracker on a given day 30 | * - Day contains a list of TrackerData values 31 | */ 32 | @Stable 33 | class ReflectModel { 34 | private val trackers = mutableStateListOf() 35 | private val history = mutableStateListOf() 36 | 37 | fun today(): Day { 38 | if (history.size == 0) newDay(Date()) 39 | return history.last() 40 | } 41 | 42 | fun newDay(date: Date): Day { 43 | val data = trackers.map { TrackerData(it) } 44 | val day = Day(date, data) 45 | history.add(day) 46 | return day 47 | } 48 | 49 | fun getOrCreateDay(date: Date): Day { 50 | return history.find { it.date == date } ?: newDay(date) 51 | } 52 | 53 | fun getIndex(tracker: Tracker): Int { 54 | return trackers.indexOf(tracker) 55 | } 56 | 57 | fun getTracker(index: Int): Tracker { 58 | return trackers[index] 59 | } 60 | 61 | fun addTracker(tracker: Tracker) { 62 | trackers.add(tracker) 63 | history.forEach { day -> 64 | day.addTracker(tracker) 65 | } 66 | } 67 | 68 | fun removeTracker(tracker: Tracker) { 69 | trackers.remove(tracker) 70 | history.forEach { day -> 71 | day.removeTracker(tracker) 72 | } 73 | } 74 | } 75 | 76 | @Stable 77 | class Day( 78 | val date: Date, 79 | data: List, 80 | ) { 81 | private val _trackerData = data.toMutableStateList() 82 | val trackerData: List 83 | get() = _trackerData 84 | 85 | fun dataFor(tracker: Tracker): TrackerData? { 86 | return trackerData.find { it.tracker == tracker } 87 | } 88 | 89 | fun addTracker(tracker: Tracker) { 90 | _trackerData.add(TrackerData(tracker)) 91 | } 92 | 93 | fun removeTracker(tracker: Tracker) { 94 | _trackerData.removeIf { it.tracker == tracker } 95 | } 96 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/model/ReflectRepo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.model 18 | 19 | /* 20 | * A fake repository for model data. 21 | */ 22 | object ReflectRepo { 23 | val model = createInitialModel() 24 | } 25 | 26 | private fun createInitialModel(): ReflectModel { 27 | val model = ReflectModel() 28 | 29 | val callMom = Tracker( 30 | emoji = "📞", 31 | name = "Call Mom", 32 | type = TrackerType.BOOLEAN, 33 | ) 34 | 35 | val sleepQuality = Tracker( 36 | emoji = "😴", 37 | name = "Sleep quality", 38 | type = TrackerType.RANGE, 39 | minValue = 0, 40 | maxValue = 10, 41 | ) 42 | 43 | val mood = Tracker( 44 | emoji = "😊", 45 | name = "Mood", 46 | type = TrackerType.RANGE, 47 | minValue = 0, 48 | maxValue = 5, 49 | ) 50 | 51 | val drankWater = Tracker( 52 | emoji = "💧", 53 | name = "Drank water", 54 | type = TrackerType.COUNT, 55 | ) 56 | 57 | model.apply { 58 | addTracker(callMom) 59 | addTracker(sleepQuality) 60 | addTracker(mood) 61 | addTracker(drankWater) 62 | } 63 | 64 | model.today().apply { 65 | dataFor(callMom)?.value = 1 66 | dataFor(sleepQuality)?.value = 7 67 | dataFor(mood)?.value = 7 68 | dataFor(drankWater)?.increment() 69 | dataFor(drankWater)?.increment() 70 | dataFor(drankWater)?.increment() 71 | } 72 | 73 | return model 74 | } 75 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/model/Tracker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.model 18 | 19 | import androidx.compose.runtime.* 20 | 21 | /* 22 | * The type of values the tracker will track over time. 23 | */ 24 | enum class TrackerType { 25 | BOOLEAN, 26 | RANGE, 27 | COUNT 28 | } 29 | 30 | /* 31 | * The settings for a particular tracker. 32 | * 33 | * Note that all properties, including type, are user-configurable. 34 | */ 35 | @Stable 36 | class Tracker( 37 | emoji: String, 38 | name: String, 39 | type: TrackerType, 40 | units: String = "", 41 | minValue: Int = 0, 42 | maxValue: Int = 5, 43 | step: Int = 1, 44 | ) { 45 | var emoji by mutableStateOf(emoji) 46 | var name by mutableStateOf(name) 47 | var type by mutableStateOf(type) 48 | var units by mutableStateOf(units) 49 | var minValue by mutableStateOf(minValue) 50 | var maxValue by mutableStateOf(maxValue) 51 | var step by mutableStateOf(step) 52 | 53 | fun copy(): Tracker = Tracker( 54 | emoji, 55 | name, 56 | type, 57 | units, 58 | minValue, 59 | maxValue, 60 | step 61 | ) 62 | 63 | fun configureAs(other: Tracker) { 64 | emoji = other.emoji 65 | name = other.name 66 | type = other.type 67 | units = other.units 68 | minValue = other.minValue 69 | maxValue = other.maxValue 70 | step = other.step 71 | } 72 | 73 | override fun toString(): String { 74 | return "{ type: $type, name: $name, emoji: $emoji }" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/model/TrackerData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.model 18 | 19 | import androidx.compose.runtime.Stable 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.runtime.mutableStateOf 22 | import androidx.compose.runtime.setValue 23 | 24 | /* 25 | * The value of a particular tracker at a particular point in time. 26 | * 27 | * Note that the Tracker associated with this TrackerData is mutable, including its type. The 28 | * tracker type is therefore applied as a filter over the underlying value, so that - for example - 29 | * if a boolean tracker is changed to a count tracker, the count will start at 1. 30 | */ 31 | @Stable 32 | class TrackerData(val tracker: Tracker) { 33 | private var _value by mutableStateOf(0) 34 | 35 | /* 36 | * The current value of this tracker, presented according to the current type. 37 | */ 38 | var value: Int 39 | get() = when (tracker.type) { 40 | TrackerType.BOOLEAN -> if (_value > 0) 1 else 0 41 | TrackerType.COUNT -> if (_value < 0) 0 else _value 42 | TrackerType.RANGE -> 43 | if (_value < tracker.minValue) 44 | tracker.minValue 45 | else if (_value > tracker.maxValue) 46 | tracker.maxValue 47 | else 48 | _value 49 | } 50 | set(newValue) { 51 | _value = newValue 52 | } 53 | 54 | /* 55 | * Convenience method for determining the toggle state of the data, assuming a boolean type. 56 | */ 57 | fun isToggled(): Boolean = _value > 0 58 | 59 | /* 60 | * Convenience method for changing the toggle state of the data, assuming a boolean type. 61 | */ 62 | fun toggle() { 63 | _value = if (_value > 0) 0 else 1 64 | } 65 | 66 | /* 67 | * Convenience method for incrementing the value of the data, assuming a count or range type. 68 | */ 69 | fun increment() { 70 | _value++ 71 | } 72 | 73 | /* 74 | * Convenience method for decrementing the value of the data, assuming a count or range type. 75 | */ 76 | fun decrement() { 77 | _value-- 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui 18 | 19 | import androidx.compose.foundation.layout.* 20 | import androidx.compose.material.icons.Icons 21 | import androidx.compose.material.icons.filled.Add 22 | import androidx.compose.material3.* 23 | import androidx.compose.runtime.* 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.unit.dp 27 | import com.google.relay.example.reflect.model.* 28 | import com.google.relay.example.reflect.ui.components.* 29 | 30 | @Composable 31 | fun HomeScreen( 32 | modifier: Modifier = Modifier, 33 | onEditTracker: (Tracker) -> Unit = {}, 34 | onNewTracker: () -> Unit = {} 35 | ) { 36 | val data = ReflectRepo.model 37 | var day by remember { mutableStateOf(data.today()) } 38 | 39 | val deleteTracker: (Tracker) -> Unit = { tracker -> 40 | data.removeTracker(tracker) 41 | } 42 | 43 | Box(modifier.fillMaxSize()) { 44 | Column(modifier) { 45 | DayNavigationControl( 46 | modifier = Modifier.padding(vertical = 20.dp), 47 | onPrevTapped = { 48 | day = data.getOrCreateDay(offsetDate(day.date, -1)) 49 | }, 50 | onNextTapped = { 51 | day = data.getOrCreateDay(offsetDate(day.date, +1)) 52 | }, 53 | ) 54 | 55 | day.trackerData.forEach { 56 | TrackerControl( 57 | trackerData = it, 58 | onEditTracker = onEditTracker, 59 | onDeleteTracker = deleteTracker, 60 | modifier = modifier.height(64.dp).padding(horizontal = 10.dp, vertical = 5.dp), 61 | ) 62 | } 63 | } 64 | LargeFloatingActionButton( 65 | modifier = Modifier 66 | .align(Alignment.BottomEnd) 67 | .offset((-16).dp, (-16).dp), 68 | onClick = onNewTracker 69 | ) { 70 | Icon( 71 | Icons.Filled.Add, 72 | "Add Tracker", 73 | modifier = Modifier.size(36.dp) 74 | ) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/SettingsScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.material3.MaterialTheme 22 | import androidx.compose.runtime.* 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.res.stringResource 25 | import com.google.relay.example.reflect.model.* 26 | import com.google.relay.example.reflect.trackersettingsbar.TrackerSettingsBar 27 | import com.google.relay.example.reflect.ui.components.* 28 | import com.google.relay.example.reflect.R 29 | 30 | /* 31 | * Default tracker if none is otherwise specified 32 | */ 33 | private fun defaultTracker(): Tracker { 34 | return Tracker("🍕", "Ate Pizza", TrackerType.BOOLEAN) 35 | } 36 | 37 | /* 38 | * Screen for editing a tracker's configuration. 39 | * 40 | * If the supplied tracker is null, the settings will be used to create a new tracker; otherwise 41 | * an existing tracker's configuration will be updated. 42 | */ 43 | @Composable 44 | fun SettingsScreen( 45 | tracker: Tracker?, 46 | modifier: Modifier = Modifier, 47 | onClose: () -> Unit = {} 48 | ) { 49 | val model = ReflectRepo.model 50 | var openBottomSheet by remember { mutableStateOf(false) } 51 | // Create a locally editable tracker object, which may or may not be applied to the 52 | // main data model depending on confirmation 53 | val localTracker by remember { mutableStateOf(tracker?.copy() ?: defaultTracker()) } 54 | val titleRes = if (tracker == null) R.string.tracker_new else R.string.tracker_edit 55 | val title = stringResource(titleRes) 56 | 57 | Column( 58 | modifier.background(MaterialTheme.colorScheme.surface) 59 | ) { 60 | TrackerSettingsBar( 61 | title = title, 62 | saveLabel = stringResource(R.string.save_tracker), 63 | onCloseButtonTapped = { 64 | // Reject local tracker changes 65 | onClose() 66 | }, 67 | onSaveButtonTapped = { 68 | // Accept local tracker changes 69 | if (tracker == null) 70 | model.addTracker(localTracker) 71 | else { 72 | tracker.configureAs(localTracker) 73 | } 74 | onClose() 75 | }, 76 | ) 77 | TrackerSettingsControl( 78 | tracker = localTracker, 79 | onEmojiFieldTapped = { openBottomSheet = !openBottomSheet } 80 | ) 81 | } 82 | 83 | if (openBottomSheet) { 84 | EmojiSelector( 85 | onEmojiSelected = { 86 | localTracker.emoji = it 87 | openBottomSheet = false 88 | }, 89 | onSheetClosed = { 90 | openBottomSheet = false 91 | } 92 | ) 93 | } 94 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/DayNavigationControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.runtime.* 20 | import androidx.compose.runtime.saveable.rememberSaveable 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.tooling.preview.Preview 23 | import com.google.relay.example.reflect.daynavigation.DayNavigation 24 | import java.text.SimpleDateFormat 25 | import java.util.Calendar 26 | import java.util.Date 27 | 28 | val kDateFormatter = SimpleDateFormat("EEE, MMM d") 29 | 30 | fun offsetDate(date: Date, offset: Int): Date { 31 | val newDate = Calendar.getInstance() 32 | newDate.time = date 33 | newDate.add(Calendar.DAY_OF_YEAR, offset) 34 | return newDate.time 35 | } 36 | 37 | /* 38 | * A component for navigating between dates. 39 | * 40 | * DayNavigationControl is responsible for providing interaction and state management to the 41 | * stateless composable [DayNavigation] generated by Relay. 42 | */ 43 | @Composable 44 | fun DayNavigationControl( 45 | modifier: Modifier = Modifier, 46 | initialDate: Date = Date(), 47 | onPrevTapped: (newDate: Date) -> Unit, 48 | onNextTapped: (newDate: Date) -> Unit, 49 | ) { 50 | var date by rememberSaveable { mutableStateOf(initialDate) } 51 | 52 | DayNavigation( 53 | date = kDateFormatter.format(date), 54 | modifier = modifier, 55 | onPrevTapped = { 56 | date = offsetDate(date, -1) 57 | onPrevTapped(date) 58 | }, 59 | onNextTapped = { 60 | date = offsetDate(date, +1) 61 | onNextTapped(date) 62 | } 63 | ) 64 | } 65 | 66 | @Preview 67 | @Composable 68 | fun DayNavigationPreview() { 69 | DayNavigationControl( 70 | onPrevTapped = {}, onNextTapped = {}) 71 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/EmojiSelector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.lazy.grid.GridCells 22 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 23 | import androidx.compose.material3.* 24 | import androidx.compose.runtime.* 25 | import androidx.compose.runtime.saveable.rememberSaveable 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.text.style.TextAlign 28 | import androidx.compose.ui.tooling.preview.Preview 29 | import androidx.compose.ui.unit.dp 30 | import androidx.compose.ui.unit.sp 31 | 32 | /* 33 | * Modal presentation of a bottom sheet for purposes of choosing an emoji. 34 | */ 35 | @OptIn(ExperimentalMaterial3Api::class) 36 | @Composable 37 | fun EmojiSelector( 38 | onEmojiSelected: (emoji: String) -> Unit, 39 | onSheetClosed: () -> Unit 40 | ) { 41 | val bottomSheetState = rememberSheetState(skipHalfExpanded = false) 42 | 43 | ModalBottomSheet( 44 | onDismissRequest = onSheetClosed, 45 | sheetState = bottomSheetState, 46 | ) { 47 | LazyVerticalGrid( 48 | columns = GridCells.Adaptive(minSize = 64.dp) 49 | ) { 50 | val emojis = ALL_EMOJIS 51 | items(emojis.size) { 52 | Text( 53 | text = emojis[it], 54 | fontSize = 28.sp, 55 | textAlign = TextAlign.Center, 56 | modifier = Modifier.clickable { 57 | onEmojiSelected(emojis[it]) 58 | } 59 | ) 60 | } 61 | } 62 | } 63 | } 64 | 65 | @Preview 66 | @Composable 67 | private fun EmojiSelectorPreview() { 68 | var selectedEmoji: String? by rememberSaveable { mutableStateOf(null) } 69 | var openBottomSheet by rememberSaveable { mutableStateOf(false) } 70 | 71 | Column { 72 | if (selectedEmoji != null) Text(text = "Selected emoji: $selectedEmoji") 73 | 74 | Button(onClick = { 75 | openBottomSheet = !openBottomSheet 76 | }) { 77 | Text(text = "Show Emoji") 78 | } 79 | } 80 | 81 | if (openBottomSheet) { 82 | EmojiSelector( 83 | onEmojiSelected = { 84 | selectedEmoji = it 85 | openBottomSheet = false 86 | }, 87 | onSheetClosed = { 88 | openBottomSheet = false 89 | } 90 | ) 91 | } 92 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/Emojis.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | val ALL_EMOJIS = arrayOf( 20 | "😀", 21 | "😃", 22 | "😄", 23 | "😁", 24 | "😆", 25 | "😅", 26 | "🤣", 27 | "😂", 28 | "🙂", 29 | "🙃", 30 | "🫠", 31 | "😉", 32 | "😊", 33 | "😇", 34 | "🥰", 35 | "😍", 36 | "🤩", 37 | "😘", 38 | "😗", 39 | "☺️", 40 | "😚", 41 | "😙", 42 | "🥲", 43 | "😋", 44 | "😛", 45 | "😜", 46 | "🤪", 47 | "😝", 48 | "🤑", 49 | "🤗", 50 | "🤭", 51 | "🫢", 52 | "🫣", 53 | "🤫", 54 | "🤔", 55 | "🫡", 56 | "🤐", 57 | "🤨", 58 | "😐", 59 | "😑", 60 | "😶", 61 | "🫥", 62 | "😶‍🌫️", 63 | "😏", 64 | "😒", 65 | "🙄", 66 | "😬", 67 | "😮‍💨", 68 | "🤥", 69 | "😌", 70 | "😔", 71 | "😪", 72 | "🤤", 73 | "😴", 74 | "😷", 75 | "🤒", 76 | "🤕", 77 | "🤢", 78 | "🤮", 79 | "🤧", 80 | "🥵", 81 | "🥶", 82 | "🥴", 83 | "😵", 84 | "😵‍💫", 85 | "🤯", 86 | "🤠", 87 | "🥳", 88 | "🥸", 89 | "😎", 90 | "🤓", 91 | "🧐", 92 | "😕", 93 | "🫤", 94 | "😟", 95 | "🙁", 96 | "☹️", 97 | "😮", 98 | "😯", 99 | "😲", 100 | "😳", 101 | "🥺", 102 | "🥹", 103 | "😦", 104 | "😧", 105 | "😨", 106 | "😰", 107 | "😥", 108 | "😢", 109 | "😭", 110 | "😱", 111 | "😖", 112 | "😣", 113 | "😞", 114 | "😓", 115 | "😩", 116 | "😫", 117 | "🥱", 118 | "😤", 119 | "😡", 120 | "😠", 121 | "🤬", 122 | "😈", 123 | "👿", 124 | "💀", 125 | "☠️", 126 | "💩", 127 | "🤡", 128 | "👹", 129 | "👺", 130 | "👻", 131 | "👽", 132 | "👾", 133 | "🤖", 134 | "😺", 135 | "😸", 136 | "😹", 137 | "😻", 138 | "😼", 139 | "😽", 140 | "🙀", 141 | "😿", 142 | "😾", 143 | "🙈", 144 | "🙉", 145 | "🙊", 146 | "💋", 147 | "💌", 148 | "💘", 149 | "💝", 150 | "💖", 151 | "💗", 152 | "💓", 153 | "💞", 154 | "💕", 155 | "💟", 156 | "❣️", 157 | "💔", 158 | "❤️‍🔥", 159 | "❤️‍🩹", 160 | "❤️", 161 | "🧡", 162 | "💛", 163 | "💚", 164 | "💙", 165 | "💜", 166 | "🤎", 167 | "🖤", 168 | "🤍", 169 | "💯", 170 | "💢", 171 | "💥", 172 | "💫", 173 | "💦", 174 | "💨", 175 | "🕳️", 176 | "💣", 177 | "💬", 178 | "👁️‍🗨️", 179 | "🗨️", 180 | "🗯️", 181 | "💭", 182 | "💤", 183 | "👋", 184 | "🤚", 185 | "🖐️", 186 | "✋", 187 | "🖖", 188 | "🫱", 189 | "🫲", 190 | "🫳", 191 | "🫴", 192 | "👌", 193 | "🤌", 194 | "🤏", 195 | "✌️", 196 | "🤞", 197 | "🫰", 198 | "🤟", 199 | "🤘", 200 | "🤙", 201 | "👈", 202 | "👉", 203 | "👆", 204 | "🖕", 205 | "👇", 206 | "☝️", 207 | "🫵", 208 | "👍", 209 | "👎", 210 | "✊", 211 | "👊", 212 | "🤛", 213 | "🤜", 214 | "👏", 215 | "🙌", 216 | "🫶", 217 | "👐", 218 | "🤲", 219 | "🤝", 220 | "🙏", 221 | "✍️", 222 | "💅", 223 | "🤳", 224 | "💪", 225 | "🦾", 226 | "🦿", 227 | "🦵", 228 | "🦶", 229 | "👂", 230 | "🦻", 231 | "👃", 232 | "🧠", 233 | "🫀", 234 | "🫁", 235 | "🦷", 236 | "🦴", 237 | "👀", 238 | "👁️", 239 | "👅", 240 | "👄", 241 | "🫦", 242 | "👶", 243 | "🧒", 244 | "👦", 245 | "👧", 246 | "🧑", 247 | "👱", 248 | "👨", 249 | "🧔", 250 | "🧔‍♂️", 251 | "🧔‍♀️", 252 | "👨‍🦰", 253 | "👨‍🦱", 254 | "👨‍🦳", 255 | "👨‍🦲", 256 | "👩", 257 | "👩‍🦰", 258 | "🧑‍🦰", 259 | "👩‍🦱", 260 | "🧑‍🦱", 261 | "👩‍🦳", 262 | "🧑‍🦳", 263 | "👩‍🦲", 264 | "🧑‍🦲", 265 | "👱‍♀️", 266 | "👱‍♂️", 267 | "🧓", 268 | "👴", 269 | "👵", 270 | "🙍", 271 | "🙍‍♂️", 272 | "🙍‍♀️", 273 | "🙎", 274 | "🙎‍♂️", 275 | "🙎‍♀️", 276 | "🙅", 277 | "🙅‍♂️", 278 | "🙅‍♀️", 279 | "🙆", 280 | "🙆‍♂️", 281 | "🙆‍♀️", 282 | "💁", 283 | "💁‍♂️", 284 | "💁‍♀️", 285 | "🙋", 286 | "🙋‍♂️", 287 | "🙋‍♀️", 288 | "🧏", 289 | "🧏‍♂️", 290 | "🧏‍♀️", 291 | "🙇", 292 | "🙇‍♂️", 293 | "🙇‍♀️", 294 | "🤦", 295 | "🤦‍♂️", 296 | "🤦‍♀️", 297 | "🤷", 298 | "🤷‍♂️", 299 | "🤷‍♀️", 300 | "🧑‍⚕️", 301 | "👨‍⚕️", 302 | "👩‍⚕️", 303 | "🧑‍🎓", 304 | "👨‍🎓", 305 | "👩‍🎓", 306 | "🧑‍🏫", 307 | "👨‍🏫", 308 | "👩‍🏫", 309 | "🧑‍⚖️", 310 | "👨‍⚖️", 311 | "👩‍⚖️", 312 | "🧑‍🌾", 313 | "👨‍🌾", 314 | "👩‍🌾", 315 | "🧑‍🍳", 316 | "👨‍🍳", 317 | "👩‍🍳", 318 | "🧑‍🔧", 319 | "👨‍🔧", 320 | "👩‍🔧", 321 | "🧑‍🏭", 322 | "👨‍🏭", 323 | "👩‍🏭", 324 | "🧑‍💼", 325 | "👨‍💼", 326 | "👩‍💼", 327 | "🧑‍🔬", 328 | "👨‍🔬", 329 | "👩‍🔬", 330 | "🧑‍💻", 331 | "👨‍💻", 332 | "👩‍💻", 333 | "🧑‍🎤", 334 | "👨‍🎤", 335 | "👩‍🎤", 336 | "🧑‍🎨", 337 | "👨‍🎨", 338 | "👩‍🎨", 339 | "🧑‍✈️", 340 | "👨‍✈️", 341 | "👩‍✈️", 342 | "🧑‍🚀", 343 | "👨‍🚀", 344 | "👩‍🚀", 345 | "🧑‍🚒", 346 | "👨‍🚒", 347 | "👩‍🚒", 348 | "👮", 349 | "👮‍♂️", 350 | "👮‍♀️", 351 | "🕵️", 352 | "🕵️‍♂️", 353 | "🕵️‍♀️", 354 | "💂", 355 | "💂‍♂️", 356 | "💂‍♀️", 357 | "🥷", 358 | "👷", 359 | "👷‍♂️", 360 | "👷‍♀️", 361 | "🫅", 362 | "🤴", 363 | "👸", 364 | "👳", 365 | "👳‍♂️", 366 | "👳‍♀️", 367 | "👲", 368 | "🧕", 369 | "🤵", 370 | "🤵‍♂️", 371 | "🤵‍♀️", 372 | "👰", 373 | "👰‍♂️", 374 | "👰‍♀️", 375 | "🤰", 376 | "🫃", 377 | "🫄", 378 | "🤱", 379 | "👩‍🍼", 380 | "👨‍🍼", 381 | "🧑‍🍼", 382 | "👼", 383 | "🎅", 384 | "🤶", 385 | "🧑‍🎄", 386 | "🦸", 387 | "🦸‍♂️", 388 | "🦸‍♀️", 389 | "🦹", 390 | "🦹‍♂️", 391 | "🦹‍♀️", 392 | "🧙", 393 | "🧙‍♂️", 394 | "🧙‍♀️", 395 | "🧚", 396 | "🧚‍♂️", 397 | "🧚‍♀️", 398 | "🧛", 399 | "🧛‍♂️", 400 | "🧛‍♀️", 401 | "🧜", 402 | "🧜‍♂️", 403 | "🧜‍♀️", 404 | "🧝", 405 | "🧝‍♂️", 406 | "🧝‍♀️", 407 | "🧞", 408 | "🧞‍♂️", 409 | "🧞‍♀️", 410 | "🧟", 411 | "🧟‍♂️", 412 | "🧟‍♀️", 413 | "🧌", 414 | "💆", 415 | "💆‍♂️", 416 | "💆‍♀️", 417 | "💇", 418 | "💇‍♂️", 419 | "💇‍♀️", 420 | "🚶", 421 | "🚶‍♂️", 422 | "🚶‍♀️", 423 | "🧍", 424 | "🧍‍♂️", 425 | "🧍‍♀️", 426 | "🧎", 427 | "🧎‍♂️", 428 | "🧎‍♀️", 429 | "🧑‍🦯", 430 | "👨‍🦯", 431 | "👩‍🦯", 432 | "🧑‍🦼", 433 | "👨‍🦼", 434 | "👩‍🦼", 435 | "🧑‍🦽", 436 | "👨‍🦽", 437 | "👩‍🦽", 438 | "🏃", 439 | "🏃‍♂️", 440 | "🏃‍♀️", 441 | "💃", 442 | "🕺", 443 | "🕴️", 444 | "👯", 445 | "👯‍♂️", 446 | "👯‍♀️", 447 | "🧖", 448 | "🧖‍♂️", 449 | "🧖‍♀️", 450 | "🧗", 451 | "🧗‍♂️", 452 | "🧗‍♀️", 453 | "🤺", 454 | "🏇", 455 | "⛷️", 456 | "🏂", 457 | "🏌️", 458 | "🏌️‍♂️", 459 | "🏌️‍♀️", 460 | "🏄", 461 | "🏄‍♂️", 462 | "🏄‍♀️", 463 | "🚣", 464 | "🚣‍♂️", 465 | "🚣‍♀️", 466 | "🏊", 467 | "🏊‍♂️", 468 | "🏊‍♀️", 469 | "⛹️", 470 | "⛹️‍♂️", 471 | "⛹️‍♀️", 472 | "🏋️", 473 | "🏋️‍♂️", 474 | "🏋️‍♀️", 475 | "🚴", 476 | "🚴‍♂️", 477 | "🚴‍♀️", 478 | "🚵", 479 | "🚵‍♂️", 480 | "🚵‍♀️", 481 | "🤸", 482 | "🤸‍♂️", 483 | "🤸‍♀️", 484 | "🤼", 485 | "🤼‍♂️", 486 | "🤼‍♀️", 487 | "🤽", 488 | "🤽‍♂️", 489 | "🤽‍♀️", 490 | "🤾", 491 | "🤾‍♂️", 492 | "🤾‍♀️", 493 | "🤹", 494 | "🤹‍♂️", 495 | "🤹‍♀️", 496 | "🧘", 497 | "🧘‍♂️", 498 | "🧘‍♀️", 499 | "🛀", 500 | "🛌", 501 | "🧑‍🤝‍🧑", 502 | "👭", 503 | "👫", 504 | "👬", 505 | "💏", 506 | "👩‍❤️‍💋‍👨", 507 | "👨‍❤️‍💋‍👨", 508 | "👩‍❤️‍💋‍👩", 509 | "💑", 510 | "👩‍❤️‍👨", 511 | "👨‍❤️‍👨", 512 | "👩‍❤️‍👩", 513 | "👪", 514 | "👨‍👩‍👦", 515 | "👨‍👩‍👧", 516 | "👨‍👩‍👧‍👦", 517 | "👨‍👩‍👦‍👦", 518 | "👨‍👩‍👧‍👧", 519 | "👨‍👨‍👦", 520 | "👨‍👨‍👧", 521 | "👨‍👨‍👧‍👦", 522 | "👨‍👨‍👦‍👦", 523 | "👨‍👨‍👧‍👧", 524 | "👩‍👩‍👦", 525 | "👩‍👩‍👧", 526 | "👩‍👩‍👧‍👦", 527 | "👩‍👩‍👦‍👦", 528 | "👩‍👩‍👧‍👧", 529 | "👨‍👦", 530 | "👨‍👦‍👦", 531 | "👨‍👧", 532 | "👨‍👧‍👦", 533 | "👨‍👧‍👧", 534 | "👩‍👦", 535 | "👩‍👦‍👦", 536 | "👩‍👧", 537 | "👩‍👧‍👦", 538 | "👩‍👧‍👧", 539 | "🗣️", 540 | "👤", 541 | "👥", 542 | "🫂", 543 | "👣", 544 | "🐵", 545 | "🐒", 546 | "🦍", 547 | "🦧", 548 | "🐶", 549 | "🐕", 550 | "🦮", 551 | "🐕‍🦺", 552 | "🐩", 553 | "🐺", 554 | "🦊", 555 | "🦝", 556 | "🐱", 557 | "🐈", 558 | "🐈‍⬛", 559 | "🦁", 560 | "🐯", 561 | "🐅", 562 | "🐆", 563 | "🐴", 564 | "🐎", 565 | "🦄", 566 | "🦓", 567 | "🦌", 568 | "🦬", 569 | "🐮", 570 | "🐂", 571 | "🐃", 572 | "🐄", 573 | "🐷", 574 | "🐖", 575 | "🐗", 576 | "🐽", 577 | "🐏", 578 | "🐑", 579 | "🐐", 580 | "🐪", 581 | "🐫", 582 | "🦙", 583 | "🦒", 584 | "🐘", 585 | "🦣", 586 | "🦏", 587 | "🦛", 588 | "🐭", 589 | "🐁", 590 | "🐀", 591 | "🐹", 592 | "🐰", 593 | "🐇", 594 | "🐿️", 595 | "🦫", 596 | "🦔", 597 | "🦇", 598 | "🐻", 599 | "🐻‍❄️", 600 | "🐨", 601 | "🐼", 602 | "🦥", 603 | "🦦", 604 | "🦨", 605 | "🦘", 606 | "🦡", 607 | "🐾", 608 | "🦃", 609 | "🐔", 610 | "🐓", 611 | "🐣", 612 | "🐤", 613 | "🐥", 614 | "🐦", 615 | "🐧", 616 | "🕊️", 617 | "🦅", 618 | "🦆", 619 | "🦢", 620 | "🦉", 621 | "🦤", 622 | "🪶", 623 | "🦩", 624 | "🦚", 625 | "🦜", 626 | "🐸", 627 | "🐊", 628 | "🐢", 629 | "🦎", 630 | "🐍", 631 | "🐲", 632 | "🐉", 633 | "🦕", 634 | "🦖", 635 | "🐳", 636 | "🐋", 637 | "🐬", 638 | "🦭", 639 | "🐟", 640 | "🐠", 641 | "🐡", 642 | "🦈", 643 | "🐙", 644 | "🐚", 645 | "🪸", 646 | "🐌", 647 | "🦋", 648 | "🐛", 649 | "🐜", 650 | "🐝", 651 | "🪲", 652 | "🐞", 653 | "🦗", 654 | "🪳", 655 | "🕷️", 656 | "🕸️", 657 | "🦂", 658 | "🦟", 659 | "🪰", 660 | "🪱", 661 | "🦠", 662 | "💐", 663 | "🌸", 664 | "💮", 665 | "🪷", 666 | "🏵️", 667 | "🌹", 668 | "🥀", 669 | "🌺", 670 | "🌻", 671 | "🌼", 672 | "🌷", 673 | "🌱", 674 | "🪴", 675 | "🌲", 676 | "🌳", 677 | "🌴", 678 | "🌵", 679 | "🌾", 680 | "🌿", 681 | "☘️", 682 | "🍀", 683 | "🍁", 684 | "🍂", 685 | "🍃", 686 | "🪹", 687 | "🪺", 688 | "🍇", 689 | "🍈", 690 | "🍉", 691 | "🍊", 692 | "🍋", 693 | "🍌", 694 | "🍍", 695 | "🥭", 696 | "🍎", 697 | "🍏", 698 | "🍐", 699 | "🍑", 700 | "🍒", 701 | "🍓", 702 | "🫐", 703 | "🥝", 704 | "🍅", 705 | "🫒", 706 | "🥥", 707 | "🥑", 708 | "🍆", 709 | "🥔", 710 | "🥕", 711 | "🌽", 712 | "🌶️", 713 | "🫑", 714 | "🥒", 715 | "🥬", 716 | "🥦", 717 | "🧄", 718 | "🧅", 719 | "🍄", 720 | "🥜", 721 | "🫘", 722 | "🌰", 723 | "🍞", 724 | "🥐", 725 | "🥖", 726 | "🫓", 727 | "🥨", 728 | "🥯", 729 | "🥞", 730 | "🧇", 731 | "🧀", 732 | "🍖", 733 | "🍗", 734 | "🥩", 735 | "🥓", 736 | "🍔", 737 | "🍟", 738 | "🍕", 739 | "🌭", 740 | "🥪", 741 | "🌮", 742 | "🌯", 743 | "🫔", 744 | "🥙", 745 | "🧆", 746 | "🥚", 747 | "🍳", 748 | "🥘", 749 | "🍲", 750 | "🫕", 751 | "🥣", 752 | "🥗", 753 | "🍿", 754 | "🧈", 755 | "🧂", 756 | "🥫", 757 | "🍱", 758 | "🍘", 759 | "🍙", 760 | "🍚", 761 | "🍛", 762 | "🍜", 763 | "🍝", 764 | "🍠", 765 | "🍢", 766 | "🍣", 767 | "🍤", 768 | "🍥", 769 | "🥮", 770 | "🍡", 771 | "🥟", 772 | "🥠", 773 | "🥡", 774 | "🦀", 775 | "🦞", 776 | "🦐", 777 | "🦑", 778 | "🦪", 779 | "🍦", 780 | "🍧", 781 | "🍨", 782 | "🍩", 783 | "🍪", 784 | "🎂", 785 | "🍰", 786 | "🧁", 787 | "🥧", 788 | "🍫", 789 | "🍬", 790 | "🍭", 791 | "🍮", 792 | "🍯", 793 | "🍼", 794 | "🥛", 795 | "☕", 796 | "🫖", 797 | "🍵", 798 | "🍶", 799 | "🍾", 800 | "🍷", 801 | "🍸", 802 | "🍹", 803 | "🍺", 804 | "🍻", 805 | "🥂", 806 | "🥃", 807 | "🫗", 808 | "🥤", 809 | "🧋", 810 | "🧃", 811 | "🧉", 812 | "🧊", 813 | "🥢", 814 | "🍽️", 815 | "🍴", 816 | "🥄", 817 | "🔪", 818 | "🫙", 819 | "🏺", 820 | "🌍", 821 | "🌎", 822 | "🌏", 823 | "🌐", 824 | "🗺️", 825 | "🗾", 826 | "🧭", 827 | "🏔️", 828 | "⛰️", 829 | "🌋", 830 | "🗻", 831 | "🏕️", 832 | "🏖️", 833 | "🏜️", 834 | "🏝️", 835 | "🏞️", 836 | "🏟️", 837 | "🏛️", 838 | "🏗️", 839 | "🧱", 840 | "🪨", 841 | "🪵", 842 | "🛖", 843 | "🏘️", 844 | "🏚️", 845 | "🏠", 846 | "🏡", 847 | "🏢", 848 | "🏣", 849 | "🏤", 850 | "🏥", 851 | "🏦", 852 | "🏨", 853 | "🏩", 854 | "🏪", 855 | "🏫", 856 | "🏬", 857 | "🏭", 858 | "🏯", 859 | "🏰", 860 | "💒", 861 | "🗼", 862 | "🗽", 863 | "⛪", 864 | "🕌", 865 | "🛕", 866 | "🕍", 867 | "⛩️", 868 | "🕋", 869 | "⛲", 870 | "⛺", 871 | "🌁", 872 | "🌃", 873 | "🏙️", 874 | "🌄", 875 | "🌅", 876 | "🌆", 877 | "🌇", 878 | "🌉", 879 | "♨️", 880 | "🎠", 881 | "🛝", 882 | "🎡", 883 | "🎢", 884 | "💈", 885 | "🎪", 886 | "🚂", 887 | "🚃", 888 | "🚄", 889 | "🚅", 890 | "🚆", 891 | "🚇", 892 | "🚈", 893 | "🚉", 894 | "🚊", 895 | "🚝", 896 | "🚞", 897 | "🚋", 898 | "🚌", 899 | "🚍", 900 | "🚎", 901 | "🚐", 902 | "🚑", 903 | "🚒", 904 | "🚓", 905 | "🚔", 906 | "🚕", 907 | "🚖", 908 | "🚗", 909 | "🚘", 910 | "🚙", 911 | "🛻", 912 | "🚚", 913 | "🚛", 914 | "🚜", 915 | "🏎️", 916 | "🏍️", 917 | "🛵", 918 | "🦽", 919 | "🦼", 920 | "🛺", 921 | "🚲", 922 | "🛴", 923 | "🛹", 924 | "🛼", 925 | "🚏", 926 | "🛣️", 927 | "🛤️", 928 | "🛢️", 929 | "⛽", 930 | "🛞", 931 | "🚨", 932 | "🚥", 933 | "🚦", 934 | "🛑", 935 | "🚧", 936 | "⚓", 937 | "🛟", 938 | "⛵", 939 | "🛶", 940 | "🚤", 941 | "🛳️", 942 | "⛴️", 943 | "🛥️", 944 | "🚢", 945 | "✈️", 946 | "🛩️", 947 | "🛫", 948 | "🛬", 949 | "🪂", 950 | "💺", 951 | "🚁", 952 | "🚟", 953 | "🚠", 954 | "🚡", 955 | "🛰️", 956 | "🚀", 957 | "🛸", 958 | "🛎️", 959 | "🧳", 960 | "⌛", 961 | "⏳", 962 | "⌚", 963 | "⏰", 964 | "⏱️", 965 | "⏲️", 966 | "🕰️", 967 | "🕛", 968 | "🕧", 969 | "🕐", 970 | "🕜", 971 | "🕑", 972 | "🕝", 973 | "🕒", 974 | "🕞", 975 | "🕓", 976 | "🕟", 977 | "🕔", 978 | "🕠", 979 | "🕕", 980 | "🕡", 981 | "🕖", 982 | "🕢", 983 | "🕗", 984 | "🕣", 985 | "🕘", 986 | "🕤", 987 | "🕙", 988 | "🕥", 989 | "🕚", 990 | "🕦", 991 | "🌑", 992 | "🌒", 993 | "🌓", 994 | "🌔", 995 | "🌕", 996 | "🌖", 997 | "🌗", 998 | "🌘", 999 | "🌙", 1000 | "🌚", 1001 | "🌛", 1002 | "🌜", 1003 | "🌡️", 1004 | "☀️", 1005 | "🌝", 1006 | "🌞", 1007 | "🪐", 1008 | "⭐", 1009 | "🌟", 1010 | "🌠", 1011 | "🌌", 1012 | "☁️", 1013 | "⛅", 1014 | "⛈️", 1015 | "🌤️", 1016 | "🌥️", 1017 | "🌦️", 1018 | "🌧️", 1019 | "🌨️", 1020 | "🌩️", 1021 | "🌪️", 1022 | "🌫️", 1023 | "🌬️", 1024 | "🌀", 1025 | "🌈", 1026 | "🌂", 1027 | "☂️", 1028 | "☔", 1029 | "⛱️", 1030 | "⚡", 1031 | "❄️", 1032 | "☃️", 1033 | "⛄", 1034 | "☄️", 1035 | "🔥", 1036 | "💧", 1037 | "🌊", 1038 | "🎃", 1039 | "🎄", 1040 | "🎆", 1041 | "🎇", 1042 | "🧨", 1043 | "✨", 1044 | "🎈", 1045 | "🎉", 1046 | "🎊", 1047 | "🎋", 1048 | "🎍", 1049 | "🎎", 1050 | "🎏", 1051 | "🎐", 1052 | "🎑", 1053 | "🧧", 1054 | "🎀", 1055 | "🎁", 1056 | "🎗️", 1057 | "🎟️", 1058 | "🎫", 1059 | "🎖️", 1060 | "🏆", 1061 | "🏅", 1062 | "🥇", 1063 | "🥈", 1064 | "🥉", 1065 | "⚽", 1066 | "⚾", 1067 | "🥎", 1068 | "🏀", 1069 | "🏐", 1070 | "🏈", 1071 | "🏉", 1072 | "🎾", 1073 | "🥏", 1074 | "🎳", 1075 | "🏏", 1076 | "🏑", 1077 | "🏒", 1078 | "🥍", 1079 | "🏓", 1080 | "🏸", 1081 | "🥊", 1082 | "🥋", 1083 | "🥅", 1084 | "⛳", 1085 | "⛸️", 1086 | "🎣", 1087 | "🤿", 1088 | "🎽", 1089 | "🎿", 1090 | "🛷", 1091 | "🥌", 1092 | "🎯", 1093 | "🪀", 1094 | "🪁", 1095 | "🎱", 1096 | "🔮", 1097 | "🪄", 1098 | "🧿", 1099 | "🪬", 1100 | "🎮", 1101 | "🕹️", 1102 | "🎰", 1103 | "🎲", 1104 | "🧩", 1105 | "🧸", 1106 | "🪅", 1107 | "🪩", 1108 | "🪆", 1109 | "♠️", 1110 | "♥️", 1111 | "♦️", 1112 | "♣️", 1113 | "♟️", 1114 | "🃏", 1115 | "🀄", 1116 | "🎴", 1117 | "🎭", 1118 | "🖼️", 1119 | "🎨", 1120 | "🧵", 1121 | "🪡", 1122 | "🧶", 1123 | "🪢", 1124 | "👓", 1125 | "🕶️", 1126 | "🥽", 1127 | "🥼", 1128 | "🦺", 1129 | "👔", 1130 | "👕", 1131 | "👖", 1132 | "🧣", 1133 | "🧤", 1134 | "🧥", 1135 | "🧦", 1136 | "👗", 1137 | "👘", 1138 | "🥻", 1139 | "🩱", 1140 | "🩲", 1141 | "🩳", 1142 | "👙", 1143 | "👚", 1144 | "👛", 1145 | "👜", 1146 | "👝", 1147 | "🛍️", 1148 | "🎒", 1149 | "🩴", 1150 | "👞", 1151 | "👟", 1152 | "🥾", 1153 | "🥿", 1154 | "👠", 1155 | "👡", 1156 | "🩰", 1157 | "👢", 1158 | "👑", 1159 | "👒", 1160 | "🎩", 1161 | "🎓", 1162 | "🧢", 1163 | "🪖", 1164 | "⛑️", 1165 | "📿", 1166 | "💄", 1167 | "💍", 1168 | "💎", 1169 | "🔇", 1170 | "🔈", 1171 | "🔉", 1172 | "🔊", 1173 | "📢", 1174 | "📣", 1175 | "📯", 1176 | "🔔", 1177 | "🔕", 1178 | "🎼", 1179 | "🎵", 1180 | "🎶", 1181 | "🎙️", 1182 | "🎚️", 1183 | "🎛️", 1184 | "🎤", 1185 | "🎧", 1186 | "📻", 1187 | "🎷", 1188 | "🪗", 1189 | "🎸", 1190 | "🎹", 1191 | "🎺", 1192 | "🎻", 1193 | "🪕", 1194 | "🥁", 1195 | "🪘", 1196 | "📱", 1197 | "📲", 1198 | "☎️", 1199 | "📞", 1200 | "📟", 1201 | "📠", 1202 | "🔋", 1203 | "🪫", 1204 | "🔌", 1205 | "💻", 1206 | "🖥️", 1207 | "🖨️", 1208 | "⌨️", 1209 | "🖱️", 1210 | "🖲️", 1211 | "💽", 1212 | "💾", 1213 | "💿", 1214 | "📀", 1215 | "🧮", 1216 | "🎥", 1217 | "🎞️", 1218 | "📽️", 1219 | "🎬", 1220 | "📺", 1221 | "📷", 1222 | "📸", 1223 | "📹", 1224 | "📼", 1225 | "🔍", 1226 | "🔎", 1227 | "🕯️", 1228 | "💡", 1229 | "🔦", 1230 | "🏮", 1231 | "🪔", 1232 | "📔", 1233 | "📕", 1234 | "📖", 1235 | "📗", 1236 | "📘", 1237 | "📙", 1238 | "📚", 1239 | "📓", 1240 | "📒", 1241 | "📃", 1242 | "📜", 1243 | "📄", 1244 | "📰", 1245 | "🗞️", 1246 | "📑", 1247 | "🔖", 1248 | "🏷️", 1249 | "💰", 1250 | "🪙", 1251 | "💴", 1252 | "💵", 1253 | "💶", 1254 | "💷", 1255 | "💸", 1256 | "💳", 1257 | "🧾", 1258 | "💹", 1259 | "✉️", 1260 | "📧", 1261 | "📨", 1262 | "📩", 1263 | "📤", 1264 | "📥", 1265 | "📦", 1266 | "📫", 1267 | "📪", 1268 | "📬", 1269 | "📭", 1270 | "📮", 1271 | "🗳️", 1272 | "✏️", 1273 | "✒️", 1274 | "🖋️", 1275 | "🖊️", 1276 | "🖌️", 1277 | "🖍️", 1278 | "📝", 1279 | "💼", 1280 | "📁", 1281 | "📂", 1282 | "🗂️", 1283 | "📅", 1284 | "📆", 1285 | "🗒️", 1286 | "🗓️", 1287 | "📇", 1288 | "📈", 1289 | "📉", 1290 | "📊", 1291 | "📋", 1292 | "📌", 1293 | "📍", 1294 | "📎", 1295 | "🖇️", 1296 | "📏", 1297 | "📐", 1298 | "✂️", 1299 | "🗃️", 1300 | "🗄️", 1301 | "🗑️", 1302 | "🔒", 1303 | "🔓", 1304 | "🔏", 1305 | "🔐", 1306 | "🔑", 1307 | "🗝️", 1308 | "🔨", 1309 | "🪓", 1310 | "⛏️", 1311 | "⚒️", 1312 | "🛠️", 1313 | "🗡️", 1314 | "⚔️", 1315 | "🔫", 1316 | "🪃", 1317 | "🏹", 1318 | "🛡️", 1319 | "🪚", 1320 | "🔧", 1321 | "🪛", 1322 | "🔩", 1323 | "⚙️", 1324 | "🗜️", 1325 | "⚖️", 1326 | "🦯", 1327 | "🔗", 1328 | "⛓️", 1329 | "🪝", 1330 | "🧰", 1331 | "🧲", 1332 | "🪜", 1333 | "⚗️", 1334 | "🧪", 1335 | "🧫", 1336 | "🧬", 1337 | "🔬", 1338 | "🔭", 1339 | "📡", 1340 | "💉", 1341 | "🩸", 1342 | "💊", 1343 | "🩹", 1344 | "🩼", 1345 | "🩺", 1346 | "🩻", 1347 | "🚪", 1348 | "🛗", 1349 | "🪞", 1350 | "🪟", 1351 | "🛏️", 1352 | "🛋️", 1353 | "🪑", 1354 | "🚽", 1355 | "🪠", 1356 | "🚿", 1357 | "🛁", 1358 | "🪤", 1359 | "🪒", 1360 | "🧴", 1361 | "🧷", 1362 | "🧹", 1363 | "🧺", 1364 | "🧻", 1365 | "🪣", 1366 | "🧼", 1367 | "🫧", 1368 | "🪥", 1369 | "🧽", 1370 | "🧯", 1371 | "🛒", 1372 | "🚬", 1373 | "⚰️", 1374 | "🪦", 1375 | "⚱️", 1376 | "🗿", 1377 | "🪧", 1378 | "🪪", 1379 | "🏧", 1380 | "🚮", 1381 | "🚰", 1382 | "♿", 1383 | "🚹", 1384 | "🚺", 1385 | "🚻", 1386 | "🚼", 1387 | "🚾", 1388 | "🛂", 1389 | "🛃", 1390 | "🛄", 1391 | "🛅", 1392 | "⚠️", 1393 | "🚸", 1394 | "⛔", 1395 | "🚫", 1396 | "🚳", 1397 | "🚭", 1398 | "🚯", 1399 | "🚱", 1400 | "🚷", 1401 | "📵", 1402 | "🔞", 1403 | "☢️", 1404 | "☣️", 1405 | "⬆️", 1406 | "↗️", 1407 | "➡️", 1408 | "↘️", 1409 | "⬇️", 1410 | "↙️", 1411 | "⬅️", 1412 | "↖️", 1413 | "↕️", 1414 | "↔️", 1415 | "↩️", 1416 | "↪️", 1417 | "⤴️", 1418 | "⤵️", 1419 | "🔃", 1420 | "🔄", 1421 | "🔙", 1422 | "🔚", 1423 | "🔛", 1424 | "🔜", 1425 | "🔝", 1426 | "🛐", 1427 | "⚛️", 1428 | "🕉️", 1429 | "✡️", 1430 | "☸️", 1431 | "☯️", 1432 | "✝️", 1433 | "☦️", 1434 | "☪️", 1435 | "☮️", 1436 | "🕎", 1437 | "🔯", 1438 | "♈", 1439 | "♉", 1440 | "♊", 1441 | "♋", 1442 | "♌", 1443 | "♍", 1444 | "♎", 1445 | "♏", 1446 | "♐", 1447 | "♑", 1448 | "♒", 1449 | "♓", 1450 | "⛎", 1451 | "🔀", 1452 | "🔁", 1453 | "🔂", 1454 | "▶️", 1455 | "⏩", 1456 | "⏭️", 1457 | "⏯️", 1458 | "◀️", 1459 | "⏪", 1460 | "⏮️", 1461 | "🔼", 1462 | "⏫", 1463 | "🔽", 1464 | "⏬", 1465 | "⏸️", 1466 | "⏹️", 1467 | "⏺️", 1468 | "⏏️", 1469 | "🎦", 1470 | "🔅", 1471 | "🔆", 1472 | "📶", 1473 | "📳", 1474 | "📴", 1475 | "♀️", 1476 | "♂️", 1477 | "⚧️", 1478 | "✖️", 1479 | "➕", 1480 | "➖", 1481 | "➗", 1482 | "🟰", 1483 | "♾️", 1484 | "‼️", 1485 | "⁉️", 1486 | "❓", 1487 | "❔", 1488 | "❕", 1489 | "❗", 1490 | "〰️", 1491 | "💱", 1492 | "💲", 1493 | "⚕️", 1494 | "♻️", 1495 | "⚜️", 1496 | "🔱", 1497 | "📛", 1498 | "🔰", 1499 | "⭕", 1500 | "✅", 1501 | "☑️", 1502 | "✔️", 1503 | "❌", 1504 | "❎", 1505 | "➰", 1506 | "➿", 1507 | "〽️", 1508 | "✳️", 1509 | "✴️", 1510 | "❇️", 1511 | "©️", 1512 | "®️", 1513 | "™️", 1514 | "#️⃣", 1515 | "*️⃣", 1516 | "0️⃣", 1517 | "1️⃣", 1518 | "2️⃣", 1519 | "3️⃣", 1520 | "4️⃣", 1521 | "5️⃣", 1522 | "6️⃣", 1523 | "7️⃣", 1524 | "8️⃣", 1525 | "9️⃣", 1526 | "🔟", 1527 | "🔠", 1528 | "🔡", 1529 | "🔢", 1530 | "🔣", 1531 | "🔤", 1532 | "🅰️", 1533 | "🆎", 1534 | "🅱️", 1535 | "🆑", 1536 | "🆒", 1537 | "🆓", 1538 | "ℹ️", 1539 | "🆔", 1540 | "Ⓜ️", 1541 | "🆕", 1542 | "🆖", 1543 | "🅾️", 1544 | "🆗", 1545 | "🅿️", 1546 | "🆘", 1547 | "🆙", 1548 | "🆚", 1549 | "🈁", 1550 | "🈂️", 1551 | "🈷️", 1552 | "🈶", 1553 | "🈯", 1554 | "🉐", 1555 | "🈹", 1556 | "🈚", 1557 | "🈲", 1558 | "🉑", 1559 | "🈸", 1560 | "🈴", 1561 | "🈳", 1562 | "㊗️", 1563 | "㊙️", 1564 | "🈺", 1565 | "🈵", 1566 | "🔴", 1567 | "🟠", 1568 | "🟡", 1569 | "🟢", 1570 | "🔵", 1571 | "🟣", 1572 | "🟤", 1573 | "⚫", 1574 | "⚪", 1575 | "🟥", 1576 | "🟧", 1577 | "🟨", 1578 | "🟩", 1579 | "🟦", 1580 | "🟪", 1581 | "🟫", 1582 | "⬛", 1583 | "⬜", 1584 | "◼️", 1585 | "◻️", 1586 | "◾", 1587 | "◽", 1588 | "▪️", 1589 | "▫️", 1590 | "🔶", 1591 | "🔷", 1592 | "🔸", 1593 | "🔹", 1594 | "🔺", 1595 | "🔻", 1596 | "💠", 1597 | "🔘", 1598 | "🔳", 1599 | "🔲", 1600 | "🏁", 1601 | "🚩", 1602 | "🎌", 1603 | "🏴", 1604 | "🏳️", 1605 | "🏳️‍🌈", 1606 | "🏳️‍⚧️", 1607 | "🏴‍☠️", 1608 | "🇦🇨", 1609 | "🇦🇩", 1610 | "🇦🇪", 1611 | "🇦🇫", 1612 | "🇦🇬", 1613 | "🇦🇮", 1614 | "🇦🇱", 1615 | "🇦🇲", 1616 | "🇦🇴", 1617 | "🇦🇶", 1618 | "🇦🇷", 1619 | "🇦🇸", 1620 | "🇦🇹", 1621 | "🇦🇺", 1622 | "🇦🇼", 1623 | "🇦🇽", 1624 | "🇦🇿", 1625 | "🇧🇦", 1626 | "🇧🇧", 1627 | "🇧🇩", 1628 | "🇧🇪", 1629 | "🇧🇫", 1630 | "🇧🇬", 1631 | "🇧🇭", 1632 | "🇧🇮", 1633 | "🇧🇯", 1634 | "🇧🇱", 1635 | "🇧🇲", 1636 | "🇧🇳", 1637 | "🇧🇴", 1638 | "🇧🇶", 1639 | "🇧🇷", 1640 | "🇧🇸", 1641 | "🇧🇹", 1642 | "🇧🇻", 1643 | "🇧🇼", 1644 | "🇧🇾", 1645 | "🇧🇿", 1646 | "🇨🇦", 1647 | "🇨🇨", 1648 | "🇨🇩", 1649 | "🇨🇫", 1650 | "🇨🇬", 1651 | "🇨🇭", 1652 | "🇨🇮", 1653 | "🇨🇰", 1654 | "🇨🇱", 1655 | "🇨🇲", 1656 | "🇨🇳", 1657 | "🇨🇴", 1658 | "🇨🇵", 1659 | "🇨🇷", 1660 | "🇨🇺", 1661 | "🇨🇻", 1662 | "🇨🇼", 1663 | "🇨🇽", 1664 | "🇨🇾", 1665 | "🇨🇿", 1666 | "🇩🇪", 1667 | "🇩🇬", 1668 | "🇩🇯", 1669 | "🇩🇰", 1670 | "🇩🇲", 1671 | "🇩🇴", 1672 | "🇩🇿", 1673 | "🇪🇦", 1674 | "🇪🇨", 1675 | "🇪🇪", 1676 | "🇪🇬", 1677 | "🇪🇭", 1678 | "🇪🇷", 1679 | "🇪🇸", 1680 | "🇪🇹", 1681 | "🇪🇺", 1682 | "🇫🇮", 1683 | "🇫🇯", 1684 | "🇫🇰", 1685 | "🇫🇲", 1686 | "🇫🇴", 1687 | "🇫🇷", 1688 | "🇬🇦", 1689 | "🇬🇧", 1690 | "🇬🇩", 1691 | "🇬🇪", 1692 | "🇬🇫", 1693 | "🇬🇬", 1694 | "🇬🇭", 1695 | "🇬🇮", 1696 | "🇬🇱", 1697 | "🇬🇲", 1698 | "🇬🇳", 1699 | "🇬🇵", 1700 | "🇬🇶", 1701 | "🇬🇷", 1702 | "🇬🇸", 1703 | "🇬🇹", 1704 | "🇬🇺", 1705 | "🇬🇼", 1706 | "🇬🇾", 1707 | "🇭🇰", 1708 | "🇭🇲", 1709 | "🇭🇳", 1710 | "🇭🇷", 1711 | "🇭🇹", 1712 | "🇭🇺", 1713 | "🇮🇨", 1714 | "🇮🇩", 1715 | "🇮🇪", 1716 | "🇮🇱", 1717 | "🇮🇲", 1718 | "🇮🇳", 1719 | "🇮🇴", 1720 | "🇮🇶", 1721 | "🇮🇷", 1722 | "🇮🇸", 1723 | "🇮🇹", 1724 | "🇯🇪", 1725 | "🇯🇲", 1726 | "🇯🇴", 1727 | "🇯🇵", 1728 | "🇰🇪", 1729 | "🇰🇬", 1730 | "🇰🇭", 1731 | "🇰🇮", 1732 | "🇰🇲", 1733 | "🇰🇳", 1734 | "🇰🇵", 1735 | "🇰🇷", 1736 | "🇰🇼", 1737 | "🇰🇾", 1738 | "🇰🇿", 1739 | "🇱🇦", 1740 | "🇱🇧", 1741 | "🇱🇨", 1742 | "🇱🇮", 1743 | "🇱🇰", 1744 | "🇱🇷", 1745 | "🇱🇸", 1746 | "🇱🇹", 1747 | "🇱🇺", 1748 | "🇱🇻", 1749 | "🇱🇾", 1750 | "🇲🇦", 1751 | "🇲🇨", 1752 | "🇲🇩", 1753 | "🇲🇪", 1754 | "🇲🇫", 1755 | "🇲🇬", 1756 | "🇲🇭", 1757 | "🇲🇰", 1758 | "🇲🇱", 1759 | "🇲🇲", 1760 | "🇲🇳", 1761 | "🇲🇴", 1762 | "🇲🇵", 1763 | "🇲🇶", 1764 | "🇲🇷", 1765 | "🇲🇸", 1766 | "🇲🇹", 1767 | "🇲🇺", 1768 | "🇲🇻", 1769 | "🇲🇼", 1770 | "🇲🇽", 1771 | "🇲🇾", 1772 | "🇲🇿", 1773 | "🇳🇦", 1774 | "🇳🇨", 1775 | "🇳🇪", 1776 | "🇳🇫", 1777 | "🇳🇬", 1778 | "🇳🇮", 1779 | "🇳🇱", 1780 | "🇳🇴", 1781 | "🇳🇵", 1782 | "🇳🇷", 1783 | "🇳🇺", 1784 | "🇳🇿", 1785 | "🇴🇲", 1786 | "🇵🇦", 1787 | "🇵🇪", 1788 | "🇵🇫", 1789 | "🇵🇬", 1790 | "🇵🇭", 1791 | "🇵🇰", 1792 | "🇵🇱", 1793 | "🇵🇲", 1794 | "🇵🇳", 1795 | "🇵🇷", 1796 | "🇵🇸", 1797 | "🇵🇹", 1798 | "🇵🇼", 1799 | "🇵🇾", 1800 | "🇶🇦", 1801 | "🇷🇪", 1802 | "🇷🇴", 1803 | "🇷🇸", 1804 | "🇷🇺", 1805 | "🇷🇼", 1806 | "🇸🇦", 1807 | "🇸🇧", 1808 | "🇸🇨", 1809 | "🇸🇩", 1810 | "🇸🇪", 1811 | "🇸🇬", 1812 | "🇸🇭", 1813 | "🇸🇮", 1814 | "🇸🇯", 1815 | "🇸🇰", 1816 | "🇸🇱", 1817 | "🇸🇲", 1818 | "🇸🇳", 1819 | "🇸🇴", 1820 | "🇸🇷", 1821 | "🇸🇸", 1822 | "🇸🇹", 1823 | "🇸🇻", 1824 | "🇸🇽", 1825 | "🇸🇾", 1826 | "🇸🇿", 1827 | "🇹🇦", 1828 | "🇹🇨", 1829 | "🇹🇩", 1830 | "🇹🇫", 1831 | "🇹🇬", 1832 | "🇹🇭", 1833 | "🇹🇯", 1834 | "🇹🇰", 1835 | "🇹🇱", 1836 | "🇹🇲", 1837 | "🇹🇳", 1838 | "🇹🇴", 1839 | "🇹🇷", 1840 | "🇹🇹", 1841 | "🇹🇻", 1842 | "🇹🇼", 1843 | "🇹🇿", 1844 | "🇺🇦", 1845 | "🇺🇬", 1846 | "🇺🇲", 1847 | "🇺🇳", 1848 | "🇺🇸", 1849 | "🇺🇾", 1850 | "🇺🇿", 1851 | "🇻🇦", 1852 | "🇻🇨", 1853 | "🇻🇪", 1854 | "🇻🇬", 1855 | "🇻🇮", 1856 | "🇻🇳", 1857 | "🇻🇺", 1858 | "🇼🇫", 1859 | "🇼🇸", 1860 | "🇽🇰", 1861 | "🇾🇪", 1862 | "🇾🇹", 1863 | "🇿🇦", 1864 | "🇿🇲", 1865 | "🇿🇼", 1866 | "🏴󠁧󠁢󠁥󠁮󠁧󠁿", 1867 | "🏴󠁧󠁢󠁳󠁣󠁴󠁿", 1868 | "🏴󠁧󠁢󠁷󠁬󠁳󠁿" 1869 | ) 1870 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/RangeControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.ExperimentalFoundationApi 20 | import androidx.compose.foundation.combinedClickable 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.foundation.shape.RoundedCornerShape 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.draw.clip 26 | import androidx.compose.ui.unit.dp 27 | import com.google.relay.example.reflect.model.TrackerData 28 | import com.google.relay.example.reflect.range.Range 29 | import com.google.relay.example.reflect.range.Spread 30 | 31 | /* 32 | * A component for controlling range-type trackers. 33 | * 34 | * RangeControl is responsible for providing interaction and state management to the stateless 35 | * composable [Range] generated by Relay. [onLongClick] provides a way for callers to supplement 36 | * the control's intrinsic interactions with, for example, a context menu. 37 | */ 38 | @OptIn(ExperimentalFoundationApi::class) 39 | @Composable 40 | fun RangeControl( 41 | trackerData: TrackerData, 42 | modifier: Modifier = Modifier, 43 | onLongClick: (() -> Unit)? = null, 44 | ) { 45 | val spreadValues = Spread.values() 46 | // The following assumes the tracker values are integers. We also want only the min value to map 47 | // to an empty range value and only the max value to map to the maximum range value. 48 | val min = trackerData.tracker.minValue 49 | val max = trackerData.tracker.maxValue 50 | val trackerSpan = max - min - 1 51 | val mappedValue = 52 | when (val value = trackerData.value) { 53 | min -> 0 54 | max -> spreadValues.size - 1 55 | else -> 1 + ((value - min - 1) * (spreadValues.size - 2)) / trackerSpan 56 | } 57 | val spreadValue = spreadValues[mappedValue] 58 | 59 | Range( 60 | modifier 61 | .clip(shape = RoundedCornerShape(size = 32.dp)) 62 | .combinedClickable(onLongClick = onLongClick) { 63 | if (trackerData.value >= trackerData.tracker.maxValue) { 64 | trackerData.value = trackerData.tracker.minValue 65 | } else { 66 | trackerData.value += 1 67 | } 68 | }, 69 | spread = spreadValue, 70 | emoji = trackerData.tracker.emoji, 71 | title = trackerData.tracker.name, 72 | value = trackerData.value.toString(), 73 | ) 74 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/ReflectButton.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.layout.Spacer 20 | import androidx.compose.foundation.layout.size 21 | import androidx.compose.material3.Button 22 | import androidx.compose.material3.ButtonDefaults 23 | import androidx.compose.material3.ElevatedButton 24 | import androidx.compose.material3.FilledTonalButton 25 | import androidx.compose.material3.OutlinedButton 26 | import androidx.compose.material3.Text 27 | import androidx.compose.material3.TextButton 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.tooling.preview.Preview 31 | import com.google.relay.example.reflect.button.Configuration 32 | import com.google.relay.example.reflect.button.State 33 | import com.google.relay.example.reflect.button.materialIcons 34 | import com.google.relay.example.reflect.ui.theme.ReflectTheme 35 | 36 | @Composable 37 | fun ReflectButton( 38 | modifier: Modifier = Modifier, 39 | configuration: Configuration = Configuration.Filled, 40 | state: State = State.Enabled, 41 | materialIconName: String = "", 42 | showIcon: Boolean = false, 43 | label: String = "", 44 | onTap: () -> Unit = {} 45 | ) { 46 | when (configuration) { 47 | Configuration.Tonal -> FilledTonalButton( 48 | onClick = onTap, 49 | modifier = modifier, 50 | enabled = state == State.Enabled, 51 | ) { 52 | if (showIcon) { 53 | Text(materialIconName, fontFamily = materialIcons) 54 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 55 | } 56 | Text(label) 57 | } 58 | Configuration.Elevated -> ElevatedButton( 59 | onClick = onTap, 60 | modifier = modifier, 61 | enabled = state == State.Enabled, 62 | ) { 63 | if (showIcon) { 64 | Text(materialIconName, fontFamily = materialIcons) 65 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 66 | } 67 | Text(label) 68 | } 69 | Configuration.Text -> TextButton( 70 | onClick = onTap, 71 | modifier = modifier, 72 | enabled = state == State.Enabled, 73 | ) { 74 | if (showIcon) { 75 | Text(materialIconName, fontFamily = materialIcons) 76 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 77 | } 78 | Text(label) 79 | } 80 | Configuration.Outlined -> OutlinedButton( 81 | onClick = onTap, 82 | modifier = modifier, 83 | enabled = state == State.Enabled, 84 | ) { 85 | if (showIcon) { 86 | Text(materialIconName, fontFamily = materialIcons) 87 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 88 | } 89 | Text(label) 90 | } 91 | Configuration.Filled -> Button( 92 | onClick = onTap, 93 | modifier = modifier, 94 | enabled = state == State.Enabled, 95 | ) { 96 | if (showIcon) { 97 | Text(materialIconName, fontFamily = materialIcons) 98 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 99 | } 100 | Text(label) 101 | } 102 | } 103 | } 104 | 105 | 106 | @Preview 107 | @Composable 108 | private fun ButtonConfigurationTonalStateDisabledPreview() { 109 | ReflectTheme { 110 | ReflectButton( 111 | onTap = {}, 112 | materialIconName = "add", 113 | showIcon = true, 114 | label = "Disabled", 115 | configuration = Configuration.Tonal, 116 | state = State.Disabled 117 | ) 118 | } 119 | } 120 | 121 | @Preview 122 | @Composable 123 | private fun ButtonConfigurationTonalStatePressedPreview() { 124 | ReflectTheme { 125 | ReflectButton( 126 | onTap = {}, 127 | materialIconName = "add", 128 | showIcon = true, 129 | label = "Pressed", 130 | configuration = Configuration.Tonal, 131 | state = State.Pressed 132 | ) 133 | } 134 | } 135 | 136 | @Preview 137 | @Composable 138 | private fun ButtonConfigurationTonalStateFocusedPreview() { 139 | ReflectTheme { 140 | ReflectButton( 141 | onTap = {}, 142 | materialIconName = "add", 143 | showIcon = true, 144 | label = "Focused", 145 | configuration = Configuration.Tonal, 146 | state = State.Focused 147 | ) 148 | } 149 | } 150 | 151 | @Preview 152 | @Composable 153 | private fun ButtonConfigurationTonalStateHoveredPreview() { 154 | ReflectTheme { 155 | ReflectButton( 156 | onTap = {}, 157 | materialIconName = "add", 158 | showIcon = true, 159 | label = "Hovered", 160 | configuration = Configuration.Tonal, 161 | state = State.Hovered 162 | ) 163 | } 164 | } 165 | 166 | @Preview 167 | @Composable 168 | private fun ButtonConfigurationTonalStateEnabledPreview() { 169 | ReflectTheme { 170 | ReflectButton( 171 | onTap = {}, 172 | materialIconName = "add", 173 | showIcon = true, 174 | label = "Enabled", 175 | configuration = Configuration.Tonal, 176 | state = State.Enabled 177 | ) 178 | } 179 | } 180 | 181 | @Preview 182 | @Composable 183 | private fun ButtonConfigurationElevatedStateDisabledPreview() { 184 | ReflectTheme { 185 | ReflectButton( 186 | onTap = {}, 187 | materialIconName = "add", 188 | showIcon = true, 189 | label = "Disabled", 190 | configuration = Configuration.Elevated, 191 | state = State.Disabled 192 | ) 193 | } 194 | } 195 | 196 | @Preview 197 | @Composable 198 | private fun ButtonConfigurationElevatedStatePressedPreview() { 199 | ReflectTheme { 200 | ReflectButton( 201 | onTap = {}, 202 | materialIconName = "add", 203 | showIcon = true, 204 | label = "Pressed", 205 | configuration = Configuration.Elevated, 206 | state = State.Pressed 207 | ) 208 | } 209 | } 210 | 211 | @Preview 212 | @Composable 213 | private fun ButtonConfigurationElevatedStateFocusedPreview() { 214 | ReflectTheme { 215 | ReflectButton( 216 | onTap = {}, 217 | materialIconName = "add", 218 | showIcon = true, 219 | label = "Focused", 220 | configuration = Configuration.Elevated, 221 | state = State.Focused 222 | ) 223 | } 224 | } 225 | 226 | @Preview 227 | @Composable 228 | private fun ButtonConfigurationElevatedStateHoveredPreview() { 229 | ReflectTheme { 230 | ReflectButton( 231 | onTap = {}, 232 | materialIconName = "add", 233 | showIcon = true, 234 | label = "Hovered", 235 | configuration = Configuration.Elevated, 236 | state = State.Hovered 237 | ) 238 | } 239 | } 240 | 241 | @Preview 242 | @Composable 243 | private fun ButtonConfigurationElevatedStateEnabledPreview() { 244 | ReflectTheme { 245 | ReflectButton( 246 | onTap = {}, 247 | materialIconName = "add", 248 | showIcon = true, 249 | label = "Enabled", 250 | configuration = Configuration.Elevated, 251 | state = State.Enabled 252 | ) 253 | } 254 | } 255 | 256 | @Preview 257 | @Composable 258 | private fun ButtonConfigurationTextStateDisabledPreview() { 259 | ReflectTheme { 260 | ReflectButton( 261 | onTap = {}, 262 | materialIconName = "add", 263 | showIcon = true, 264 | label = "Disabled", 265 | configuration = Configuration.Text, 266 | state = State.Disabled 267 | ) 268 | } 269 | } 270 | 271 | @Preview 272 | @Composable 273 | private fun ButtonConfigurationTextStatePressedPreview() { 274 | ReflectTheme { 275 | ReflectButton( 276 | onTap = {}, 277 | materialIconName = "add", 278 | showIcon = true, 279 | label = "Pressed", 280 | configuration = Configuration.Text, 281 | state = State.Pressed 282 | ) 283 | } 284 | } 285 | 286 | @Preview 287 | @Composable 288 | private fun ButtonConfigurationTextStateFocusedPreview() { 289 | ReflectTheme { 290 | ReflectButton( 291 | onTap = {}, 292 | materialIconName = "add", 293 | showIcon = true, 294 | label = "Focused", 295 | configuration = Configuration.Text, 296 | state = State.Focused 297 | ) 298 | } 299 | } 300 | 301 | @Preview 302 | @Composable 303 | private fun ButtonConfigurationTextStateHoveredPreview() { 304 | ReflectTheme { 305 | ReflectButton( 306 | onTap = {}, 307 | materialIconName = "add", 308 | showIcon = true, 309 | label = "Hovered", 310 | configuration = Configuration.Text, 311 | state = State.Hovered 312 | ) 313 | } 314 | } 315 | 316 | @Preview 317 | @Composable 318 | private fun ButtonConfigurationTextStateEnabledPreview() { 319 | ReflectTheme { 320 | ReflectButton( 321 | onTap = {}, 322 | materialIconName = "add", 323 | showIcon = true, 324 | label = "Enabled", 325 | configuration = Configuration.Text, 326 | state = State.Enabled 327 | ) 328 | } 329 | } 330 | 331 | @Preview 332 | @Composable 333 | private fun ButtonConfigurationOutlinedStateDisabledPreview() { 334 | ReflectTheme { 335 | ReflectButton( 336 | onTap = {}, 337 | materialIconName = "add", 338 | showIcon = true, 339 | label = "Disabled", 340 | configuration = Configuration.Outlined, 341 | state = State.Disabled 342 | ) 343 | } 344 | } 345 | 346 | @Preview 347 | @Composable 348 | private fun ButtonConfigurationOutlinedStatePressedPreview() { 349 | ReflectTheme { 350 | ReflectButton( 351 | onTap = {}, 352 | materialIconName = "add", 353 | showIcon = true, 354 | label = "Pressed", 355 | configuration = Configuration.Outlined, 356 | state = State.Pressed 357 | ) 358 | } 359 | } 360 | 361 | @Preview 362 | @Composable 363 | private fun ButtonConfigurationOutlinedStateFocusedPreview() { 364 | ReflectTheme { 365 | ReflectButton( 366 | onTap = {}, 367 | materialIconName = "add", 368 | showIcon = true, 369 | label = "Focused", 370 | configuration = Configuration.Outlined, 371 | state = State.Focused 372 | ) 373 | } 374 | } 375 | 376 | @Preview 377 | @Composable 378 | private fun ButtonConfigurationOutlinedStateHoveredPreview() { 379 | ReflectTheme { 380 | ReflectButton( 381 | onTap = {}, 382 | materialIconName = "add", 383 | showIcon = true, 384 | label = "Hovered", 385 | configuration = Configuration.Outlined, 386 | state = State.Hovered 387 | ) 388 | } 389 | } 390 | 391 | @Preview 392 | @Composable 393 | private fun ButtonConfigurationOutlinedStateEnabledPreview() { 394 | ReflectTheme { 395 | ReflectButton( 396 | onTap = {}, 397 | materialIconName = "add", 398 | showIcon = true, 399 | label = "Enabled", 400 | configuration = Configuration.Outlined, 401 | state = State.Enabled 402 | ) 403 | } 404 | } 405 | 406 | @Preview 407 | @Composable 408 | private fun ButtonConfigurationFilledStateDisabledPreview() { 409 | ReflectTheme { 410 | ReflectButton( 411 | onTap = {}, 412 | materialIconName = "add", 413 | showIcon = true, 414 | label = "Disabled", 415 | configuration = Configuration.Filled, 416 | state = State.Disabled 417 | ) 418 | } 419 | } 420 | 421 | @Preview 422 | @Composable 423 | private fun ButtonConfigurationFilledStatePressedPreview() { 424 | ReflectTheme { 425 | ReflectButton( 426 | onTap = {}, 427 | materialIconName = "add", 428 | showIcon = true, 429 | label = "Pressed", 430 | configuration = Configuration.Filled, 431 | state = State.Pressed 432 | ) 433 | } 434 | } 435 | 436 | @Preview 437 | @Composable 438 | private fun ButtonConfigurationFilledStateFocusedPreview() { 439 | ReflectTheme { 440 | ReflectButton( 441 | onTap = {}, 442 | materialIconName = "add", 443 | showIcon = true, 444 | label = "Focused", 445 | configuration = Configuration.Filled, 446 | state = State.Focused 447 | ) 448 | } 449 | } 450 | 451 | @Preview 452 | @Composable 453 | private fun ButtonConfigurationFilledStateHoveredPreview() { 454 | ReflectTheme { 455 | ReflectButton( 456 | onTap = {}, 457 | materialIconName = "add", 458 | showIcon = true, 459 | label = "Hovered", 460 | configuration = Configuration.Filled, 461 | state = State.Hovered 462 | ) 463 | } 464 | } 465 | 466 | @Preview 467 | @Composable 468 | private fun ButtonConfigurationFilledStateEnabledPreview() { 469 | ReflectTheme { 470 | ReflectButton( 471 | onTap = {}, 472 | materialIconName = "add", 473 | showIcon = true, 474 | label = "Enabled", 475 | configuration = Configuration.Filled, 476 | state = State.Enabled 477 | ) 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/ReflectDropdownMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.material3.* 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.getValue 22 | import androidx.compose.runtime.mutableStateOf 23 | import androidx.compose.runtime.remember 24 | import androidx.compose.runtime.setValue 25 | import androidx.compose.ui.Modifier 26 | import com.google.relay.example.reflect.textfield.DesignSelection 27 | import com.google.relay.example.reflect.textfield.State 28 | import com.google.relay.example.reflect.textfield.Style 29 | import com.google.relay.example.reflect.textfield.TextConfigurations 30 | 31 | @OptIn(ExperimentalMaterial3Api::class) 32 | @Composable 33 | fun ReflectDropdownMenu( 34 | modifier: Modifier = Modifier, 35 | items: String = "", 36 | selectedIndex: Double = 0.0, 37 | labelText: String = "", 38 | onChange: (Double) -> Unit = {}, 39 | textFieldDesign: DesignSelection = DesignSelection( 40 | style = Style.Outlined, 41 | state = State.Enabled, 42 | textConfigurations = TextConfigurations.InputText 43 | ), 44 | ) { 45 | val options = items.split(',') 46 | 47 | var expanded by remember { mutableStateOf(false) } 48 | var selectedOptionText by remember { mutableStateOf(options[selectedIndex.toInt()]) } 49 | // We want to react on tap/press on TextField to show menu 50 | ExposedDropdownMenuBox( 51 | expanded = expanded, 52 | onExpandedChange = { expanded = !expanded }, 53 | modifier = modifier, 54 | ) { 55 | when (textFieldDesign.style) { 56 | Style.Outlined -> OutlinedTextField( 57 | value = selectedOptionText, 58 | onValueChange = {}, 59 | readOnly = true, 60 | label = { Text(labelText) }, 61 | enabled = textFieldDesign.state == State.Enabled, 62 | trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, 63 | colors = ExposedDropdownMenuDefaults.textFieldColors( 64 | containerColor = MaterialTheme.colorScheme.surface, 65 | unfocusedIndicatorColor = MaterialTheme.colorScheme.outline, 66 | ), 67 | // The `menuAnchor` modifier must be passed to the text field for correctness. 68 | modifier = Modifier.menuAnchor(), 69 | ) 70 | 71 | Style.Filled -> TextField( 72 | value = selectedOptionText, 73 | onValueChange = {}, 74 | readOnly = true, 75 | label = { Text(labelText) }, 76 | enabled = textFieldDesign.state == State.Enabled, 77 | trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, 78 | colors = ExposedDropdownMenuDefaults.textFieldColors( 79 | containerColor = MaterialTheme.colorScheme.surface, 80 | unfocusedIndicatorColor = MaterialTheme.colorScheme.outline, 81 | ), 82 | // The `menuAnchor` modifier must be passed to the text field for correctness. 83 | modifier = Modifier.menuAnchor(), 84 | ) 85 | 86 | } 87 | ExposedDropdownMenu( 88 | expanded = expanded, 89 | onDismissRequest = { expanded = false }, 90 | ) { 91 | options.forEachIndexed { index, selectionOption -> 92 | DropdownMenuItem( 93 | text = { Text(selectionOption) }, 94 | onClick = { 95 | onChange(index.toDouble()) 96 | selectedOptionText = selectionOption 97 | expanded = false 98 | }, 99 | contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, 100 | ) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/ReflectTextField.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.material3.OutlinedTextField 20 | import androidx.compose.material3.Text 21 | import androidx.compose.material3.TextField 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.tooling.preview.Preview 25 | import com.google.relay.example.reflect.textfield.State 26 | import com.google.relay.example.reflect.textfield.Style 27 | import com.google.relay.example.reflect.textfield.TextConfigurations 28 | import com.google.relay.example.reflect.textfield.materialIcons 29 | import com.google.relay.example.reflect.ui.theme.ReflectTheme 30 | 31 | /* 32 | * Wrapper for a Material 3 TextField suitable for reference by a Relay component. 33 | * 34 | * ReflectTextField conforms to the interface defined by the `text_field` UI package, which in turn 35 | * is defined by the Figma "Text Field" component. The Text Field Figma component used here is a 36 | * slightly modified version of the Material 3 Design Kit component Text Field. Designers using 37 | * this Text Field component can supply configuration, such as style and choice of icon, which are 38 | * carried through in generated code to this component and thus to the underlying [TextField] or 39 | * [OutlinedTextField]. 40 | */ 41 | @Composable 42 | fun ReflectTextField( 43 | modifier: Modifier = Modifier, 44 | style: Style = Style.Outlined, 45 | state: State = State.Enabled, 46 | // textConfigurations is unused. Instead, M3 TextField determines its text configuration 47 | // by whether labelText and placeholderText are not empty. 48 | @Suppress("UNUSED_PARAMETER") textConfigurations: TextConfigurations = TextConfigurations.InputText, 49 | inputText: String = "", 50 | labelText: String = "", 51 | placeholderText: String = "", 52 | showSupportingText: Boolean = false, 53 | supportingText: String = "", 54 | showLeadingIcon: Boolean = false, 55 | leadingIcon: String = "search", 56 | showTrailingIcon: Boolean = false, 57 | trailingIcon: String = "cancel", 58 | onChange: (String) -> Unit = {} 59 | ) { 60 | val leadingIconText: @Composable (() -> Unit)? = if (showLeadingIcon) { 61 | { 62 | Text(leadingIcon, fontFamily = materialIcons) 63 | } 64 | } else null 65 | val trailingIconText: @Composable (() -> Unit)? = if (showTrailingIcon) { 66 | { 67 | Text(trailingIcon, fontFamily = materialIcons) 68 | } 69 | } else null 70 | 71 | when (style) { 72 | Style.Filled -> TextField(modifier = modifier, 73 | value = inputText, 74 | onValueChange = onChange, 75 | enabled = state != State.Disabled, 76 | label = { if (labelText.isNotEmpty()) Text(labelText) }, 77 | placeholder = { if (placeholderText.isNotEmpty()) Text(placeholderText) }, 78 | supportingText = { if (showSupportingText) Text(supportingText) }, 79 | isError = state == State.Error, 80 | leadingIcon = leadingIconText, 81 | trailingIcon = trailingIconText, 82 | ) 83 | Style.Outlined -> OutlinedTextField(modifier = modifier, 84 | value = inputText, 85 | onValueChange = onChange, 86 | enabled = state != State.Disabled, 87 | label = { if (labelText.isNotEmpty()) Text(labelText) }, 88 | placeholder = { if (placeholderText.isNotEmpty()) Text(placeholderText) }, 89 | supportingText = { if (showSupportingText) Text(supportingText) }, 90 | isError = state == State.Error, 91 | leadingIcon = leadingIconText, 92 | trailingIcon = trailingIconText, 93 | ) 94 | } 95 | } 96 | 97 | /* 98 | * The rest of this file consists of previews for ReflectTextField in various configurations. 99 | * NOTE: There are no previews for placeholder text, because the text field needs focus 100 | * to show placeholder text, and it's not clear how to preview a composable with focus; see 101 | * https://stackoverflow.com/questions/75074398/focus-state-in-jetpacks-compose-previews. 102 | */ 103 | 104 | @Preview(showBackground = true) 105 | @Composable 106 | private fun Outlined_Disabled_LabelTextPreview() { 107 | ReflectTheme { 108 | ReflectTextField( 109 | style = Style.Outlined, 110 | state = State.Disabled, 111 | textConfigurations = TextConfigurations.LabelText, 112 | inputText = "", 113 | labelText = "Label", 114 | showLeadingIcon = true, 115 | showTrailingIcon = true, 116 | showSupportingText = true, 117 | supportingText = "Supporting text", 118 | ) 119 | } 120 | } 121 | 122 | @Preview(showBackground = true) 123 | @Composable 124 | private fun Outlined_Error_LabelTextPreview() { 125 | ReflectTheme { 126 | ReflectTextField( 127 | style = Style.Outlined, 128 | state = State.Error, 129 | textConfigurations = TextConfigurations.LabelText, 130 | inputText = "", 131 | labelText = "Label", 132 | showLeadingIcon = true, 133 | showTrailingIcon = true, 134 | trailingIcon = "error", 135 | showSupportingText = true, 136 | supportingText = "Supporting text", 137 | ) 138 | } 139 | } 140 | 141 | @Preview(showBackground = true) 142 | @Composable 143 | private fun Outlined_Enabled_LabelTextPreview() { 144 | ReflectTheme { 145 | ReflectTextField( 146 | style = Style.Outlined, 147 | state = State.Enabled, 148 | textConfigurations = TextConfigurations.LabelText, 149 | inputText = "", 150 | labelText = "Label", 151 | showLeadingIcon = true, 152 | showTrailingIcon = true, 153 | showSupportingText = true, 154 | supportingText = "Supporting text", 155 | ) 156 | } 157 | } 158 | 159 | @Preview(showBackground = true) 160 | @Composable 161 | private fun Outlined_Disabled_InputTextPreview() { 162 | ReflectTheme { 163 | ReflectTextField( 164 | style = Style.Outlined, 165 | state = State.Disabled, 166 | textConfigurations = TextConfigurations.InputText, 167 | inputText = "Input", 168 | labelText = "Label", 169 | showLeadingIcon = true, 170 | showTrailingIcon = true, 171 | showSupportingText = true, 172 | supportingText = "Supporting text", 173 | ) 174 | } 175 | } 176 | 177 | @Preview(showBackground = true) 178 | @Composable 179 | private fun Outlined_Error_InputTextPreview() { 180 | ReflectTheme { 181 | ReflectTextField( 182 | style = Style.Outlined, 183 | state = State.Error, 184 | textConfigurations = TextConfigurations.InputText, 185 | inputText = "Input", 186 | labelText = "Label", 187 | showLeadingIcon = true, 188 | showTrailingIcon = true, 189 | showSupportingText = true, 190 | supportingText = "Supporting text", 191 | ) 192 | } 193 | } 194 | 195 | @Preview(showBackground = true) 196 | @Composable 197 | private fun Outlined_Enabled_InputTextPreview() { 198 | ReflectTheme { 199 | ReflectTextField( 200 | style = Style.Outlined, 201 | state = State.Enabled, 202 | textConfigurations = TextConfigurations.InputText, 203 | inputText = "Input", 204 | labelText = "Label", 205 | showLeadingIcon = true, 206 | showTrailingIcon = true, 207 | showSupportingText = true, 208 | supportingText = "Supporting text", 209 | ) 210 | } 211 | } 212 | 213 | @Preview(showBackground = true) 214 | @Composable 215 | private fun Filled_Disabled_LabelTextPreview() { 216 | ReflectTheme { 217 | ReflectTextField( 218 | style = Style.Filled, 219 | state = State.Disabled, 220 | textConfigurations = TextConfigurations.LabelText, 221 | inputText = "", 222 | labelText = "Label", 223 | showLeadingIcon = true, 224 | showTrailingIcon = true, 225 | showSupportingText = true, 226 | supportingText = "Supporting text", 227 | ) 228 | } 229 | } 230 | 231 | @Preview(showBackground = true) 232 | @Composable 233 | private fun Filled_Error_LabelTextPreview() { 234 | ReflectTheme { 235 | ReflectTextField( 236 | style = Style.Filled, 237 | state = State.Error, 238 | textConfigurations = TextConfigurations.LabelText, 239 | inputText = "", 240 | labelText = "Label", 241 | showLeadingIcon = true, 242 | showTrailingIcon = true, 243 | trailingIcon = "error", 244 | showSupportingText = true, 245 | supportingText = "Supporting text", 246 | ) 247 | } 248 | } 249 | 250 | @Preview(showBackground = true) 251 | @Composable 252 | private fun Filled_Enabled_LabelTextPreview() { 253 | ReflectTheme { 254 | ReflectTextField( 255 | style = Style.Filled, 256 | state = State.Enabled, 257 | textConfigurations = TextConfigurations.LabelText, 258 | inputText = "", 259 | labelText = "Label", 260 | showLeadingIcon = true, 261 | showTrailingIcon = true, 262 | showSupportingText = true, 263 | supportingText = "Supporting text", 264 | ) 265 | } 266 | } 267 | 268 | @Preview(showBackground = true) 269 | @Composable 270 | private fun Filled_Disabled_InputTextPreview() { 271 | ReflectTheme { 272 | ReflectTextField( 273 | style = Style.Filled, 274 | state = State.Disabled, 275 | textConfigurations = TextConfigurations.InputText, 276 | inputText = "Input", 277 | labelText = "Label", 278 | showLeadingIcon = true, 279 | showTrailingIcon = true, 280 | showSupportingText = true, 281 | supportingText = "Supporting text", 282 | ) 283 | } 284 | } 285 | 286 | @Preview(showBackground = true) 287 | @Composable 288 | private fun Filled_Error_InputTextPreview() { 289 | ReflectTheme { 290 | ReflectTextField( 291 | style = Style.Filled, 292 | state = State.Error, 293 | textConfigurations = TextConfigurations.InputText, 294 | inputText = "Input", 295 | labelText = "Label", 296 | showLeadingIcon = true, 297 | showTrailingIcon = true, 298 | showSupportingText = true, 299 | supportingText = "Supporting text", 300 | ) 301 | } 302 | } 303 | 304 | @Preview(showBackground = true) 305 | @Composable 306 | private fun Filled_Enabled_InputTextPreview() { 307 | ReflectTheme { 308 | ReflectTextField( 309 | style = Style.Filled, 310 | state = State.Enabled, 311 | textConfigurations = TextConfigurations.InputText, 312 | inputText = "Input", 313 | labelText = "Label", 314 | showLeadingIcon = true, 315 | showTrailingIcon = true, 316 | showSupportingText = true, 317 | supportingText = "Supporting text", 318 | ) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/SwitchControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.ExperimentalFoundationApi 20 | import androidx.compose.foundation.combinedClickable 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.foundation.shape.RoundedCornerShape 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.draw.clip 26 | import androidx.compose.ui.tooling.preview.Preview 27 | import androidx.compose.ui.unit.dp 28 | import com.google.relay.example.reflect.model.Tracker 29 | import com.google.relay.example.reflect.model.TrackerData 30 | import com.google.relay.example.reflect.model.TrackerType 31 | import com.google.relay.example.reflect.switch.Switch 32 | 33 | /* 34 | * A component for controlling switch-type trackers. 35 | * 36 | * SwitchControl is responsible for providing interaction and state management to the stateless 37 | * composable [Switch] generated by Relay. [onLongClick] provides a way for callers to supplement 38 | * the control's intrinsic interactions with, for example, a context menu. 39 | */ 40 | @OptIn(ExperimentalFoundationApi::class) 41 | @Composable 42 | fun SwitchControl( 43 | // TODO: Fill in this signature 44 | ) { 45 | // TODO: Fill in this body 46 | } 47 | 48 | @Preview 49 | @Composable 50 | fun SwitchControllerPreview() { 51 | val data = TrackerData( 52 | Tracker( 53 | emoji = "🍕", 54 | name = "Ate Pizza", 55 | type = TrackerType.BOOLEAN 56 | ) 57 | ) 58 | SwitchControl( 59 | // TODO: Pass data to SwitchControl 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/TrackerControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.layout.Box 20 | import androidx.compose.material3.* 21 | import androidx.compose.runtime.* 22 | import androidx.compose.ui.Modifier 23 | import com.google.relay.example.reflect.model.Tracker 24 | import com.google.relay.example.reflect.model.TrackerData 25 | import com.google.relay.example.reflect.model.TrackerType 26 | 27 | /* 28 | * A component for controlling trackers. 29 | * 30 | * TrackerControl is responsible for providing interaction and state management common to all 31 | * tracker types, by providing a context menu with edit and delete controls. 32 | */ 33 | @OptIn(ExperimentalMaterial3Api::class) 34 | @Composable 35 | fun TrackerControl( 36 | trackerData: TrackerData, 37 | modifier: Modifier = Modifier, 38 | onEditTracker: (Tracker) -> Unit = {}, 39 | onDeleteTracker: (Tracker) -> Unit = {}, 40 | ) { 41 | var expanded by remember { mutableStateOf(false) } 42 | var confirming by remember { mutableStateOf(false) } 43 | 44 | Box(modifier = modifier) { 45 | 46 | // TODO: replace with Relay tracker components 47 | Text(text = trackerData.tracker.toString()) 48 | 49 | DropdownMenu( 50 | expanded = expanded, 51 | onDismissRequest = { expanded = false }, 52 | ) { 53 | DropdownMenuItem( 54 | text = { Text("Edit") }, 55 | onClick = { onEditTracker(trackerData.tracker) }, 56 | contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, 57 | ) 58 | DropdownMenuItem( 59 | text = { Text("Delete") }, 60 | onClick = { confirming = true }, 61 | contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, 62 | ) 63 | } 64 | } 65 | 66 | if (confirming) { 67 | AlertDialog( 68 | onDismissRequest = { confirming = false }, 69 | title = { 70 | Text(text = "Remove tracker?") 71 | }, 72 | text = { 73 | Text("All historical data will also be removed.") 74 | }, 75 | confirmButton = { 76 | Button( 77 | onClick = { 78 | confirming = false 79 | onDeleteTracker(trackerData.tracker) 80 | } 81 | ) { 82 | Text("Remove") 83 | } 84 | }, 85 | dismissButton = { 86 | Button( 87 | onClick = { confirming = false } 88 | ) { 89 | Text("Cancel") 90 | } 91 | } 92 | ) 93 | } 94 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/TrackerSettingsControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.runtime.* 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.tooling.preview.Preview 23 | import com.google.relay.compose.RelayContainer 24 | import com.google.relay.example.reflect.model.* 25 | import com.google.relay.example.reflect.trackersettings.TrackerSettings 26 | import com.google.relay.example.reflect.trackersettings.Type 27 | import com.google.relay.example.reflect.ui.theme.ReflectTheme 28 | import com.google.relay.example.reflect.R 29 | 30 | /* 31 | * A component for configuring a new or existing tracker. 32 | * 33 | * TrackerSettingsControl is responsible for providing interaction and state management to the 34 | * stateless composable [TrackerSettings] generated by Relay. 35 | */ 36 | @Composable 37 | fun TrackerSettingsControl( 38 | tracker: Tracker, 39 | modifier: Modifier = Modifier, 40 | onEmojiFieldTapped: () -> Unit = {}, 41 | ) { 42 | val typeMenuOptions = listOf( 43 | stringResource(R.string.type_boolean), 44 | stringResource(R.string.type_count), 45 | stringResource(R.string.type_range), 46 | ).joinToString(",") 47 | 48 | TrackerSettings( 49 | modifier = modifier, 50 | emoji = tracker.emoji, 51 | type = when (tracker.type) { 52 | TrackerType.BOOLEAN -> Type.Switch 53 | TrackerType.COUNT -> Type.Value 54 | TrackerType.RANGE -> Type.Range 55 | }, 56 | typeMenuOptions = typeMenuOptions, 57 | title = tracker.name, 58 | units = tracker.units, 59 | rangeMin = tracker.minValue.toString(), 60 | rangeMax = tracker.maxValue.toString(), 61 | rangeStep = tracker.step.toString(), 62 | onEmojiFieldTapped = onEmojiFieldTapped, 63 | onTitleChanged = { tracker.name = it }, 64 | onTypeChanged = { 65 | when (it) { 66 | 0.0 -> tracker.type = TrackerType.BOOLEAN 67 | 1.0 -> tracker.type = TrackerType.COUNT 68 | 2.0 -> tracker.type = TrackerType.RANGE 69 | } 70 | }, 71 | onUnitsChanged = { tracker.units = it }, 72 | onMinChanged = { tracker.minValue = it.toInt() }, 73 | onMaxChanged = { tracker.maxValue = it.toInt() }, 74 | onStepChanged = { tracker.step = it.toInt() }, 75 | ) 76 | } 77 | 78 | @Preview(widthDp = 380, heightDp = 176) 79 | @Composable 80 | private fun TrackerSettingsTypeSwitchPreview() { 81 | val tracker = Tracker("😊", "Mood", TrackerType.BOOLEAN) 82 | ReflectTheme(isDarkTheme = true) { 83 | RelayContainer { 84 | TrackerSettingsControl(tracker) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/components/ValueControl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.components 18 | 19 | import androidx.compose.foundation.ExperimentalFoundationApi 20 | import androidx.compose.foundation.combinedClickable 21 | import androidx.compose.foundation.layout.Box 22 | import androidx.compose.foundation.layout.height 23 | import androidx.compose.foundation.layout.width 24 | import androidx.compose.foundation.shape.RoundedCornerShape 25 | import androidx.compose.runtime.* 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.draw.clip 28 | import androidx.compose.ui.tooling.preview.Preview 29 | import androidx.compose.ui.unit.dp 30 | import com.google.relay.example.reflect.model.Tracker 31 | import com.google.relay.example.reflect.model.TrackerData 32 | import com.google.relay.example.reflect.model.TrackerType 33 | import com.google.relay.example.reflect.ui.theme.ReflectTheme 34 | import com.google.relay.example.reflect.value.* 35 | import kotlinx.coroutines.CoroutineScope 36 | import kotlinx.coroutines.Job 37 | import kotlinx.coroutines.delay 38 | import kotlinx.coroutines.launch 39 | import kotlin.time.Duration.Companion.seconds 40 | 41 | /* 42 | * A component for controlling value-type trackers. 43 | * 44 | * ValueControl is responsible for providing interaction and state management to the stateless 45 | * composable [Value] generated by Relay. [onLongClick] provides a way for callers to supplement 46 | * the control's intrinsic interactions with, for example, a context menu. 47 | */ 48 | @OptIn(ExperimentalFoundationApi::class) 49 | @Composable 50 | fun ValueControl( 51 | trackerData: TrackerData, 52 | modifier: Modifier = Modifier, 53 | onLongClick: (() -> Unit)? = null, 54 | ) { 55 | val editDelaySeconds = 3 56 | 57 | val scope = rememberCoroutineScope() 58 | var countdown by remember { mutableStateOf(null) } 59 | val isEditing = countdown != null 60 | val endEditing: suspend CoroutineScope.() -> Unit = { 61 | delay(editDelaySeconds.seconds) 62 | countdown = null 63 | } 64 | 65 | Value( 66 | modifier 67 | .clip(shape = RoundedCornerShape(size = 32.dp)) 68 | .combinedClickable(onLongClick = onLongClick) { 69 | if (!isEditing) { 70 | countdown = scope.launch(block = endEditing) 71 | } 72 | }, 73 | mode = if (isEditing) Mode.Editing else Mode.Default, 74 | hasValue = trackerData.value != 0, 75 | value = trackerData.value.toString() + trackerData.tracker.units, 76 | emoji = trackerData.tracker.emoji, 77 | title = trackerData.tracker.name, 78 | handleIncrement = { 79 | trackerData.increment() 80 | countdown?.cancel() 81 | countdown = scope.launch(block = endEditing) 82 | }, 83 | handleDecrement = { 84 | trackerData.decrement() 85 | countdown?.cancel() 86 | countdown = scope.launch(block = endEditing) 87 | }, 88 | ) 89 | } 90 | 91 | @Preview 92 | @Composable 93 | fun ValueControllerPreview() { 94 | val data = TrackerData( 95 | Tracker( 96 | emoji = "🍕", 97 | name = "Value", 98 | type = TrackerType.COUNT 99 | ) 100 | ) 101 | ReflectTheme { 102 | Box( 103 | modifier = Modifier.width(388.dp).height(64.dp) 104 | ) { 105 | ValueControl(data) 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val White = Color(0xFFFFFFFF) 22 | 23 | val Tan99 = Color(0xFFFCF7F4) 24 | val Tan95 = Color(0xFFFAF5F2) 25 | val Tan90 = Color(0xFFE8DED2) 26 | val Tan89 = Color(0xFFEAE1D9) 27 | val Tan80 = Color(0xFFCCC4B5) 28 | val Tan70 = Color(0xFF7E7365) 29 | val Tan65 = Color(0xFF7C7063) 30 | val Tan50 = Color(0xFF50433A) 31 | val Tan30 = Color(0xFF3D3833) 32 | val Tan29 = Color(0xFF362710) 33 | val Tan20 = Color(0xFF231F1A) 34 | val Tan10 = Color(0xFF16130E) 35 | 36 | val Orange99 = Color(0xFFFFC673) 37 | val Orange95 = Color(0xFFF3BD6E) 38 | val Orange70 = Color(0xFF7F560F) 39 | val Orange60 = Color(0xFF6A573B) 40 | val Orange40 = Color(0xFF492F03) -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.theme 18 | 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material3.Shapes 21 | import androidx.compose.ui.unit.dp 22 | 23 | val Shapes = Shapes( 24 | small = RoundedCornerShape(4.dp), 25 | medium = RoundedCornerShape(4.dp), 26 | large = RoundedCornerShape(0.dp) 27 | ) -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.theme 18 | 19 | import android.app.Activity 20 | import androidx.compose.foundation.isSystemInDarkTheme 21 | import androidx.compose.material3.MaterialTheme 22 | import androidx.compose.material3.darkColorScheme 23 | import androidx.compose.material3.lightColorScheme 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.SideEffect 26 | import androidx.compose.ui.graphics.toArgb 27 | import androidx.compose.ui.platform.LocalView 28 | import androidx.core.view.WindowCompat 29 | 30 | private val ReflectDarkColorScheme = darkColorScheme( 31 | background = Tan20, 32 | onBackground = Tan89, 33 | surface = Tan10, 34 | onSurface = Tan89, 35 | surfaceVariant = Orange60, 36 | onSurfaceVariant = Tan89, 37 | primary = Orange95, 38 | onPrimary = White, 39 | primaryContainer = Orange95, 40 | onPrimaryContainer = Orange40, 41 | secondaryContainer = Tan30, 42 | onSecondaryContainer = Tan89, 43 | outline = Tan70 44 | ) 45 | 46 | private val ReflectLightColorScheme = lightColorScheme( 47 | background = Tan80, 48 | onBackground = Tan29, 49 | surface = Tan99, 50 | onSurface = Tan50, 51 | surfaceVariant = Tan95, 52 | onSurfaceVariant = Tan50, 53 | primary = Orange70, 54 | onPrimary = White, 55 | primaryContainer = Orange99, 56 | onPrimaryContainer = Tan50, 57 | secondaryContainer = Tan90, 58 | onSecondaryContainer = Tan50, 59 | outline = Tan65 60 | ) 61 | 62 | @Composable 63 | fun ReflectTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { 64 | val reflectColorScheme = when { 65 | isDarkTheme -> ReflectDarkColorScheme 66 | else -> ReflectLightColorScheme 67 | } 68 | 69 | val view = LocalView.current 70 | if (!view.isInEditMode) { 71 | SideEffect { 72 | val window = (view.context as Activity).window 73 | window.statusBarColor = reflectColorScheme.background.toArgb() 74 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !isDarkTheme 75 | } 76 | } 77 | 78 | MaterialTheme( 79 | colorScheme = reflectColorScheme, 80 | typography = ReflectTypography, 81 | shapes = Shapes, 82 | content = content 83 | ) 84 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/java/com/google/relay/example/reflect/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.Font 22 | import androidx.compose.ui.text.font.FontFamily 23 | import androidx.compose.ui.text.font.FontWeight 24 | import androidx.compose.ui.unit.sp 25 | import com.google.relay.example.reflect.R 26 | 27 | val DMSansFontFamily = FontFamily( 28 | Font(resId = R.font.dm_sans_regular), 29 | Font(resId = R.font.dm_sans_medium, weight = FontWeight.Medium), 30 | Font(resId = R.font.dm_sans_bold, weight = FontWeight.Bold), 31 | ) 32 | 33 | val ReflectTypography = Typography( 34 | headlineLarge = TextStyle( 35 | fontFamily = DMSansFontFamily, 36 | fontWeight = FontWeight.Bold, 37 | fontSize = 32.sp, 38 | lineHeight = 32.sp, 39 | letterSpacing = 0.sp 40 | ), 41 | headlineMedium = TextStyle( 42 | fontFamily = DMSansFontFamily, 43 | fontWeight = FontWeight.Medium, 44 | fontSize = 24.sp, 45 | lineHeight = 24.sp, 46 | letterSpacing = 0.sp 47 | ), 48 | headlineSmall = TextStyle( 49 | fontFamily = DMSansFontFamily, 50 | fontWeight = FontWeight.Normal, 51 | fontSize = 24.sp, 52 | lineHeight = 24.sp, 53 | letterSpacing = 0.sp 54 | ), 55 | titleLarge = TextStyle( 56 | fontFamily = DMSansFontFamily, 57 | fontWeight = FontWeight.Normal, 58 | fontSize = 20.sp, 59 | lineHeight = 24.sp, 60 | letterSpacing = 0.sp 61 | ), 62 | titleMedium = TextStyle( 63 | fontFamily = DMSansFontFamily, 64 | fontWeight = FontWeight.Normal, 65 | fontSize = 16.sp, 66 | lineHeight = 24.sp, 67 | letterSpacing = 0.sp 68 | ), 69 | titleSmall = TextStyle( 70 | fontFamily = DMSansFontFamily, 71 | fontWeight = FontWeight.Normal, 72 | fontSize = 14.sp, 73 | lineHeight = 20.sp, 74 | letterSpacing = 0.sp 75 | ), 76 | bodyLarge = TextStyle( 77 | fontFamily = DMSansFontFamily, 78 | fontWeight = FontWeight.Normal, 79 | fontSize = 16.sp, 80 | lineHeight = 24.sp, 81 | letterSpacing = 0.sp 82 | ), 83 | bodyMedium = TextStyle( 84 | fontFamily = DMSansFontFamily, 85 | fontWeight = FontWeight.Normal, 86 | fontSize = 14.sp, 87 | lineHeight = 20.sp, 88 | letterSpacing = 0.sp 89 | ), 90 | bodySmall = TextStyle( 91 | fontFamily = DMSansFontFamily, 92 | fontWeight = FontWeight.Normal, 93 | fontSize = 12.sp, 94 | lineHeight = 16.sp, 95 | letterSpacing = 0.sp 96 | ), 97 | labelLarge = TextStyle( 98 | fontFamily = DMSansFontFamily, 99 | fontWeight = FontWeight.Normal, 100 | fontSize = 14.sp, 101 | lineHeight = 20.sp, 102 | letterSpacing = 0.sp 103 | ), 104 | labelMedium = TextStyle( 105 | fontFamily = DMSansFontFamily, 106 | fontWeight = FontWeight.Medium, 107 | fontSize = 12.sp, 108 | lineHeight = 16.sp, 109 | letterSpacing = 0.sp 110 | ), 111 | labelSmall = TextStyle( 112 | fontFamily = DMSansFontFamily, 113 | fontWeight = FontWeight.Normal, 114 | fontSize = 11.sp, 115 | lineHeight = 16.sp, 116 | letterSpacing = 0.sp 117 | ) 118 | ) 119 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 22 | 25 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/font/dm_sans_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/font/dm_sans_bold.ttf -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/font/dm_sans_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/font/dm_sans_medium.ttf -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/font/dm_sans_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/font/dm_sans_regular.ttf -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FFBB86FC 19 | #FF6200EE 20 | #FF3700B3 21 | #FF03DAC5 22 | #FF018786 23 | #FF000000 24 | #FFFFFFFF 25 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | Reflect 18 | 19 | 20 | Add new tracker 21 | Edit tracker 22 | 23 | Save 24 | 25 | Switch 26 | Value 27 | Range 28 | 29 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 24 | 25 | 29 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 23 | 24 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/ui-package-resources/mappings/README.md: -------------------------------------------------------------------------------- 1 | This directory contains mapping files that map your UI package to custom 2 | written @Composable functions. 3 | 4 | ### Mapping file name 5 | The name of the file should match the name of your UI package. 6 | 7 | ### Mapping file contents 8 | 9 | - **target** : (Required) The name of your custom composable function. By 10 | default this is the name of the function created by generated code. 11 | 12 | ``` 13 | "target" : "CustomComposableName" 14 | ``` 15 | 16 | - **package** : (Required) Name of the package your custom composable sits in. 17 | By default this is the package of the function created by generated code. 18 | 19 | ``` 20 | "package" : "com.example.podcastapp.ui.components" 21 | ``` 22 | 23 | - **generateImplementation** : (Optional) true or false. If true, an 24 | implementation of this UI package will still be generated in the generated 25 | code file. If false, the implementation will not be generated. By default, 26 | this is true. 27 | 28 | ``` 29 | "generateImplementation" : true 30 | ``` 31 | 32 | - **generatePreviews** : (Optional) true or false. If true, a preview of the 33 | mapped custom component will be generated in the generated code file. If 34 | false, no preview will be generated. By default, this is true. 35 | 36 | ``` 37 | "generatePreviews" : true 38 | ``` 39 | 40 | - **fieldMappings** : If your UI package has parameters that do not match the 41 | custom composable's parameters in name, you will need to provide 42 | parameter mappings. 43 | - **type** : The type of mapping to apply. 44 | - **parameter** : maps a UI package field to a code parameter. 45 | - **lambda** : maps a UI package field to a content lamda or modifier. 46 | - **source** : the name of the parameter in the UI package. 47 | - **target** : the name of the parameter in the target code component. 48 | 49 | ``` 50 | "fieldMappings": [ 51 | { 52 | "type": "parameter", 53 | "source": "content", 54 | "target": "contentText" 55 | }, 56 | { 57 | "type": "lambda", 58 | "source": "frameContents", 59 | "target": "innerContents" 60 | } 61 | ] 62 | ``` 63 | -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/ui-package-resources/mappings/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "ReflectButton", 3 | "package": "com.google.relay.example.reflect.ui.components", 4 | "generateCode": false, 5 | "generatePreviews": false, 6 | "fieldMappings": [ 7 | { 8 | "type": "parameter", 9 | "source": "Icon", 10 | "target": "materialIconName" 11 | }, 12 | { 13 | "type": "parameter", 14 | "source": "Label", 15 | "target": "label" 16 | }, 17 | { 18 | "type": "design", 19 | "source": "design", 20 | "basePackage": "com.google.relay.example.reflect" 21 | }, 22 | { 23 | "type": "parameter", 24 | "source": "on Tap", 25 | "target": "onTap" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/main/ui-package-resources/mappings/dropdown_menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "ReflectDropdownMenu", 3 | "package": "com.google.relay.example.reflect.ui.components", 4 | "generateImplementation": false, 5 | "generatePreviews": false 6 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/app/src/test/java/com/google/relay/example/reflect/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.relay.example.reflect 18 | 19 | import org.junit.Test 20 | 21 | import org.junit.Assert.* 22 | 23 | /** 24 | * Example local unit test, which will execute on the development machine (host). 25 | * 26 | * See [testing documentation](http://d.android.com/tools/testing). 27 | */ 28 | class ExampleUnitTest { 29 | @Test 30 | fun addition_isCorrect() { 31 | assertEquals(4, 2 + 2) 32 | } 33 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 18 | buildscript { 19 | ext { 20 | compose_ui_version = '1.2.0' 21 | material3_version = '1.1.0-alpha07' 22 | } 23 | } 24 | 25 | plugins { 26 | id 'com.android.application' version '8.0.0' apply false 27 | id 'com.android.library' version '8.0.0' apply false 28 | id 'org.jetbrains.kotlin.android' version '1.8.0' apply false 29 | } -------------------------------------------------------------------------------- /CompleteAppCodelab/gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Project-wide Gradle settings. 16 | # IDE (e.g. Android Studio) users: 17 | # Gradle settings configured through the IDE *will override* 18 | # any settings specified in this file. 19 | # For more details on how to configure your build environment visit 20 | # http://www.gradle.org/docs/current/userguide/build_environment.html 21 | # Specifies the JVM arguments used for the daemon process. 22 | # The setting is particularly useful for tweaking memory settings. 23 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 24 | # When configured, Gradle will run in incubating parallel mode. 25 | # This option should only be used with decoupled projects. More details, visit 26 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 27 | # org.gradle.parallel=true 28 | # AndroidX package structure to make it clearer which packages are bundled with the 29 | # Android operating system, and which are packaged with your app's APK 30 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 31 | android.useAndroidX=true 32 | # Kotlin code style for this project: "official" or "obsolete": 33 | kotlin.code.style=official 34 | # Enables namespacing of each library's R class so that its R class includes only the 35 | # resources declared in the library itself and none from the library's dependencies, 36 | # thereby reducing the size of the R class for that library 37 | android.nonTransitiveRClass=true 38 | android.defaults.buildfeatures.buildconfig=true 39 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /CompleteAppCodelab/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecodelabs/relay-codelabs/89948606cf9c22deb8d2bf31b46fc46be345eee3/CompleteAppCodelab/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CompleteAppCodelab/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 26 09:44:49 PST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /CompleteAppCodelab/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /CompleteAppCodelab/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /CompleteAppCodelab/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | pluginManagement { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | gradlePluginPortal() 22 | } 23 | } 24 | dependencyResolutionManagement { 25 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 26 | repositories { 27 | google() 28 | mavenCentral() 29 | } 30 | } 31 | rootProject.name = "Reflect" 32 | include ':app' 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Relay Codelabs 2 | 3 | This repository contains sample code used in 4 | [codelabs](https://codelabs.developers.google.com/) for 5 | [Relay](https://relay.material.io/). Each codelab corresponds to a directory 6 | in this repository. 7 | 8 | Please see the individual README.md files in each top level directory in this 9 | repository for more information. --------------------------------------------------------------------------------