├── 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.
--------------------------------------------------------------------------------