From 2648e40810895a2477026e535c54f427a69f04ea Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 8 May 2024 19:36:12 +0100
Subject: [PATCH 01/63] Initial commit for dataconnect sample app

---
 copy_mock_google_services_json.sh             |   1 +
 dataconnect/.gitignore                        |  15 ++
 dataconnect/app/.gitignore                    |   1 +
 dataconnect/app/build.gradle.kts              |  69 +++++++
 dataconnect/app/proguard-rules.pro            |  21 ++
 .../dataconnect/ExampleInstrumentedTest.kt    |  24 +++
 dataconnect/app/src/main/AndroidManifest.xml  |  28 +++
 .../example/dataconnect/MainActivity.kt       |  47 +++++
 .../example/dataconnect/ui/theme/Color.kt     |  11 ++
 .../example/dataconnect/ui/theme/Theme.kt     |  58 ++++++
 .../example/dataconnect/ui/theme/Type.kt      |  34 ++++
 .../drawable-v24/ic_launcher_foreground.xml   |  30 +++
 .../res/drawable/ic_launcher_background.xml   | 170 ++++++++++++++++
 .../res/mipmap-anydpi-v26/ic_launcher.xml     |   6 +
 .../mipmap-anydpi-v26/ic_launcher_round.xml   |   6 +
 .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes
 .../res/mipmap-hdpi/ic_launcher_round.webp    | Bin 0 -> 2898 bytes
 .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes
 .../res/mipmap-mdpi/ic_launcher_round.webp    | Bin 0 -> 1772 bytes
 .../main/res/mipmap-xhdpi/ic_launcher.webp    | Bin 0 -> 1900 bytes
 .../res/mipmap-xhdpi/ic_launcher_round.webp   | Bin 0 -> 3918 bytes
 .../main/res/mipmap-xxhdpi/ic_launcher.webp   | Bin 0 -> 2884 bytes
 .../res/mipmap-xxhdpi/ic_launcher_round.webp  | Bin 0 -> 5914 bytes
 .../main/res/mipmap-xxxhdpi/ic_launcher.webp  | Bin 0 -> 3844 bytes
 .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes
 .../app/src/main/res/values/colors.xml        |  10 +
 .../app/src/main/res/values/strings.xml       |   3 +
 .../app/src/main/res/values/themes.xml        |   5 +
 .../app/src/main/res/xml/backup_rules.xml     |  13 ++
 .../main/res/xml/data_extraction_rules.xml    |  19 ++
 .../example/dataconnect/ExampleUnitTest.kt    |  17 ++
 dataconnect/build.gradle.kts                  |   5 +
 dataconnect/gradle.properties                 |  23 +++
 dataconnect/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes
 .../gradle/wrapper/gradle-wrapper.properties  |   6 +
 dataconnect/gradlew                           | 185 ++++++++++++++++++
 dataconnect/gradlew.bat                       |  89 +++++++++
 dataconnect/settings.gradle.kts               |  31 +++
 gradle/libs.versions.toml                     |  31 +++
 mock-google-services.json                     |  19 ++
 settings.gradle.kts                           |   1 +
 41 files changed, 978 insertions(+)
 create mode 100644 dataconnect/.gitignore
 create mode 100644 dataconnect/app/.gitignore
 create mode 100644 dataconnect/app/build.gradle.kts
 create mode 100644 dataconnect/app/proguard-rules.pro
 create mode 100644 dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
 create mode 100644 dataconnect/app/src/main/AndroidManifest.xml
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Color.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Theme.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Type.kt
 create mode 100644 dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
 create mode 100644 dataconnect/app/src/main/res/drawable/ic_launcher_background.xml
 create mode 100644 dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
 create mode 100644 dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
 create mode 100644 dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/values/colors.xml
 create mode 100644 dataconnect/app/src/main/res/values/strings.xml
 create mode 100644 dataconnect/app/src/main/res/values/themes.xml
 create mode 100644 dataconnect/app/src/main/res/xml/backup_rules.xml
 create mode 100644 dataconnect/app/src/main/res/xml/data_extraction_rules.xml
 create mode 100644 dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt
 create mode 100644 dataconnect/build.gradle.kts
 create mode 100644 dataconnect/gradle.properties
 create mode 100644 dataconnect/gradle/wrapper/gradle-wrapper.jar
 create mode 100644 dataconnect/gradle/wrapper/gradle-wrapper.properties
 create mode 100755 dataconnect/gradlew
 create mode 100644 dataconnect/gradlew.bat
 create mode 100644 dataconnect/settings.gradle.kts
 create mode 100644 gradle/libs.versions.toml

diff --git a/copy_mock_google_services_json.sh b/copy_mock_google_services_json.sh
index 9b275d413..1eddf3b18 100755
--- a/copy_mock_google_services_json.sh
+++ b/copy_mock_google_services_json.sh
@@ -12,6 +12,7 @@ cp mock-google-services.json auth/app/google-services.json
 cp mock-google-services.json config/app/google-services.json
 cp mock-google-services.json crash/app/google-services.json
 cp mock-google-services.json database/app/google-services.json
+cp mock-google-services.json dataconnect/app/google-services.json
 cp mock-google-services.json dynamiclinks/app/google-services.json
 cp mock-google-services.json firestore/app/google-services.json
 cp mock-google-services.json functions/app/google-services.json
diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore
new file mode 100644
index 000000000..aa724b770
--- /dev/null
+++ b/dataconnect/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/dataconnect/app/.gitignore b/dataconnect/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/dataconnect/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
new file mode 100644
index 000000000..2463e0da5
--- /dev/null
+++ b/dataconnect/app/build.gradle.kts
@@ -0,0 +1,69 @@
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.jetbrains.kotlin.android)
+}
+
+android {
+    namespace = "com.google.firebase.example.dataconnect"
+    compileSdk = 34
+
+    defaultConfig {
+        applicationId = "com.google.firebase.example.dataconnect"
+        minSdk = 23
+        targetSdk = 34
+        versionCode = 1
+        versionName = "1.0"
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        vectorDrawables {
+            useSupportLibrary = true
+        }
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+    buildFeatures {
+        compose = true
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion = "1.5.13"
+    }
+    packaging {
+        resources {
+            excludes += "/META-INF/{AL2.0,LGPL2.1}"
+        }
+    }
+}
+
+dependencies {
+
+    implementation(libs.androidx.core.ktx)
+    implementation(libs.androidx.lifecycle.runtime.ktx)
+    implementation(libs.androidx.activity.compose)
+    implementation(platform(libs.androidx.compose.bom))
+    implementation(libs.androidx.ui)
+    implementation(libs.androidx.ui.graphics)
+    implementation(libs.androidx.ui.tooling.preview)
+    implementation(libs.androidx.material3)
+    testImplementation(libs.junit)
+    androidTestImplementation(libs.androidx.junit)
+    androidTestImplementation(libs.androidx.espresso.core)
+    androidTestImplementation(platform(libs.androidx.compose.bom))
+    androidTestImplementation(libs.androidx.ui.test.junit4)
+    debugImplementation(libs.androidx.ui.tooling)
+    debugImplementation(libs.androidx.ui.test.manifest)
+}
\ No newline at end of file
diff --git a/dataconnect/app/proguard-rules.pro b/dataconnect/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/dataconnect/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt b/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..2ab99c2c8
--- /dev/null
+++ b/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.google.firebase.example.dataconnect
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.google.firebase.example.dataconnect", appContext.packageName)
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/AndroidManifest.xml b/dataconnect/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..b5ed84e4f
--- /dev/null
+++ b/dataconnect/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.FirebaseDataConnect"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.FirebaseDataConnect">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
new file mode 100644
index 000000000..03e2481f7
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -0,0 +1,47 @@
+package com.google.firebase.example.dataconnect
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
+
+class MainActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()
+        setContent {
+            FirebaseDataConnectTheme {
+                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
+                    Greeting(
+                        name = "Android",
+                        modifier = Modifier.padding(innerPadding)
+                    )
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun Greeting(name: String, modifier: Modifier = Modifier) {
+    Text(
+        text = "Hello $name!",
+        modifier = modifier
+    )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun GreetingPreview() {
+    FirebaseDataConnectTheme {
+        Greeting("Android")
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Color.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Color.kt
new file mode 100644
index 000000000..e4c2b612a
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.google.firebase.example.dataconnect.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Theme.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Theme.kt
new file mode 100644
index 000000000..b327e3af2
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Theme.kt
@@ -0,0 +1,58 @@
+package com.google.firebase.example.dataconnect.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val DarkColorScheme = darkColorScheme(
+    primary = Purple80,
+    secondary = PurpleGrey80,
+    tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+    primary = Purple40,
+    secondary = PurpleGrey40,
+    tertiary = Pink40
+
+    /* Other default colors to override
+    background = Color(0xFFFFFBFE),
+    surface = Color(0xFFFFFBFE),
+    onPrimary = Color.White,
+    onSecondary = Color.White,
+    onTertiary = Color.White,
+    onBackground = Color(0xFF1C1B1F),
+    onSurface = Color(0xFF1C1B1F),
+    */
+)
+
+@Composable
+fun FirebaseDataConnectTheme(
+    darkTheme: Boolean = isSystemInDarkTheme(),
+    // Dynamic color is available on Android 12+
+    dynamicColor: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    val colorScheme = when {
+        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+            val context = LocalContext.current
+            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        }
+
+        darkTheme -> DarkColorScheme
+        else -> LightColorScheme
+    }
+
+    MaterialTheme(
+        colorScheme = colorScheme,
+        typography = Typography,
+        content = content
+    )
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Type.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Type.kt
new file mode 100644
index 000000000..deec73173
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.google.firebase.example.dataconnect.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+    bodyLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        letterSpacing = 0.5.sp
+    )
+    /* Other default text styles to override
+    titleLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 22.sp,
+        lineHeight = 28.sp,
+        letterSpacing = 0.sp
+    ),
+    labelSmall = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Medium,
+        fontSize = 11.sp,
+        lineHeight = 16.sp,
+        letterSpacing = 0.5.sp
+    )
+    */
+)
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/drawable/ic_launcher_background.xml b/dataconnect/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/dataconnect/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3
GIT binary patch
literal 1404
zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r
zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN
zXl<@<#Cm<R)d19?=)E<{+g@mp0C)CAX%7ksNnpX=jPlJEkqD9%o*fC(U7iySOYHHS
zCLH@bXPyI|^Z)Mc^PG7Oc+NfBJO`d7p5;U`M53Wbd!w|M(MUoNRc2m{@^!wFKzL?&
zx_RAc-^9Azxo%DmXW^9GV0~;n_G9&dym)|QrMEx!y_F=oDunn;1y)cvAc6z{0MHiz
zodGIH07w8nF%*bGq9Gv`YHnm80|d1T$uW=u4vGV%tX#>e5f5yr2h%@8TWh?)bSK`O
z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF!
z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!<r*4*+?g*sysFgiN}}d!-T(>jH(_Z*nzTK4
zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG
z?Fe{#<RA5Hg_mG&BZS>9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~
zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd
zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK
zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG<W<SKpuS2eaDGkL-J-
z!+&UV_N0e<DV*|igwqdC*l{lj0T&&`uq=ycU@f9x0i8jzLa(y*X(YV?PCtUw;ks}p
z4zFn7N(-OG^(9ot1ZhYISOWe9?+l%f6v41n+j<OM$_uJgNP?ZJy}hEMMH=;WiG4I`
zs(bIWwSD<LJnAN(E(Xjr4k(^UYF37F_3f{;E%%FEa&I3I0GbdH@{pD5$m+1PN5CW)
zyZ&*9o#8wstdx@^rci;0B4BP2+H4Y<KbJI5L(bXG(k@`Kp=d>8I5h*uf{rn48Z!_@
z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqro<oln8GL@_6LPC)kg1!M)Y!|NCn7b*0sG
zEN=&c2xMM<>a($ne7EUK;#3V<N-jQ7j($tREa0F&-HzYCQtR#fTCZMRN*ZSm;T7a+
zuxa$}zDL8R9wGYkHb$+gJoiM@z+u{u7a_VUBwtd)bPzHxH}C`W=^2PsBr`s7taBMG
zm#Ss=-o`)W8%%x%>YkXaew%Kh^3OrMht<?zOY6P}#rBhn_hrWY$_P`{#CBR9w?+E6
zt7r#NN-tjxFY{9q75P<|L<ZJBHwn4FhG(&i>jYN?XEoY`tRPQsAkH-DSL^QqyN0>^
zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y
z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu
z4)<UE%?IC0Du41FrE~F_qc8nOq>P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6
z>%NbT+5*@L5(I9<vs%8>j@06@(!<!eaZcF<_le1MVaYMg=gRy*f2#IaBH-mJIpy+L
z=Gsbhd6=3>{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W
Kmw_-CKmY()leWbG

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9
GIT binary patch
literal 2898
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca
GIT binary patch
literal 982
zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^
z^1csucXz7-Qrhzl9HuH<!ckn_w-(t15itRHmqN0O$B3XH(E|jyV^QXq8=yM`Q**vy
zpEpgQd+no=J<Tlv&+_>B%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N
zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}<p
z;bEy|mw1;}P&gp|0ssKe4*;D3Dlh;r06r}ehC-pC4mGy`3;|+W04+#B4r_x~@mHHy
z)H}bD|I2-n_L$pW;*I)~?=#N<)`92&<$3IR`#<SH6@I&FRQa6xBmQ5wPwJ2PAI(ne
z$L2Yb@JHxb`+bLk*AjR$^`b?pr|?!6=+AboIQ2D-p)UI7x(J0|5(5~ur$_+)`>C%4
zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{
ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT
zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh
zOBLA>$~P3r4b*NcqR;y6pwyhZ<hjKiZs6mOSFB&+cIl`GV$93-<ciUjF#*1^<p~gh
ziQ_{)r0dA7$It&Fe=obxu8n!+elxmgqxPbUL!FxW0;AOfqz@8JOz9Qbm)m-9!^7D)
z480@BoIIb<oT``+rVla8L)8fXO&6}3P9n4v$`6WG<DUNWuKb9J9rUsAn7d-_YWT^U
z{NXl@OAPIJ!>3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu
z(U^&XLW^Q<WhKYr9rXr6*~Tmpuq6NjnD6;;NNBGIg-1ZvfACQ4{ocrwM0)?`oL2ts
zCXY5KT@`(ir63J0?%+_(-dDgf<6R$u{lCdy6Zi5d+Bf;1OXyD;xe3#Gug*&T|0o41
zD8;$|JvUv&@vsLIH&C5+S{!k&{~Z54^y@9r>X7F9Z*SRPpQl{B%x)_AMp^}_v~?j7
zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0
z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@
E0Of=7!T<mO

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000000000000000000000000000000000..62b611da081676d42f6c3f78a2c91e7bcedddedb
GIT binary patch
literal 1772
zcmV<I1{3*GNk&HG1^@t8MM6+kP&il$0000G0000l001ul06|PpNL&H{00E$D+qP-j
z>Qj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP*
z(X?&brZw<itFzD+K&M3~p69>({`)WU&rdAs1i<RDIiSY82S2mupC8Pt4!H6t1GTb(
zWRM~Q$%>T<R+Yg3{a%pbg++@O@<l(ulw^R7DJ5kYQ(?LhFeMn^80iDc8a#OdFhyzL
zDn(d!5nfX;MJV7nMVO%oPb#QF78@wSOhvdEwtz$5l){XK=|H&u(ZCCOX72e)L;uHO
z1tnw`glk~|XjH3U$_P_d)`A8s=1~}>a0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE
z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TR<yeHWl(T-||IU&i!Rd!TMUruU72
z<l~rLRD-qWW4hw3Q)?MQ93gOvat1wqq{JcosXwejji>Z+cz#eD3f8V;2r-1fE!F}W
zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_<vjH0E1*B
zfk*0C6jY|!Rf=RG!W+$uDg^D?-XzoVrR42)&Y)P6w7*9BP@dq)8yymh;%(CA$R7+o
zloov8A4l6H7NzQ3vsrJ*;3X6j#0T=toMt(L(p9c**S(b_DMgZG<-Trpa|&g3Ns}I1
zKlp(~|M0=q9!(O5aw}K0apy5RZoJ5U{>?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7
zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n
zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u
zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR
zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u*
z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN
zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT
zDL1m&|DVpI<IwA;|3z1u>y=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=*
zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8
z52e1W<DmYW|KhLyAh*AQ$=bd-79$cFL1=dC7E!?lJ(DK_A2rbd*I!fTiWjU@hO@LO
z{34r?8R+y6;5?)6c=hv86*TVD<6h<-YN#p%M+B*z{-U|t?d%$+^@~OhgQ=;&eE7WW
zQMm4(i7@Afmhf}Dnwx!Q1lKgexn~licBP}_&7QY=>U%^L1}15Ex0fF$e@eCT(()_P
zvV?CA<sp1RgQ~qYDHIC(K$HgNSDgI7aFI{AcoU=(>%#Sy08_U6VPt4EtmVQraWJX`
zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4
zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN
z8U5{-<bq**{p6!H-(%Tic#_E`wcN6#HU8-OK@OS$MA~<4ln|3Duf90UXNW1nMhk@X
z!<X~il$GI)0FveT${!;q6+#ptj}^6#CM6bt!8aB|<oIwiQzNU~!^v#E0ATVF@f>yY
zvV{`&WKU2$mZeoBmiJrEd<YP=_2@e1bJ|tRh6}2@09)72_kFh|s|{=Q%;lrD1V0sq
z5(|fB{Q};57E-A$Y;tLp9MPkkDs1?cxgaM#DX)SROj{lUu_=U;L%&QSd(1lwW9=M~
zPXv~y>zUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA
z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)<p
z|22C&(o0<{zD=}o7hFmrnHiNsKS+q5do@k^v7dAg(j37~!7%msUYhV9SAD*hicVK@
zd=IyocF&y5dH^sh4`7M2vQg8OP##~+Eu~vo(S~k<e%FqF9ffGv{w_F?KH5TRvvnu}
O>FJ$wK}0tWNB{uH;AM~i

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0
GIT binary patch
literal 1900
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f
GIT binary patch
literal 3918
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000000000000000000000000000000000..28d4b77f9f036a47549d47db79c16788749dca10
GIT binary patch
literal 2884
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000000000000000000000000000000000..9287f5083623b375139afb391af71cc533a7dd37
GIT binary patch
literal 5914
zcmV+#7v<<uNk&Ez7XScPMM6+kP&il$0000G0001w0055w06|PpNFxXU009|=ZQHgn
zNOpH4`X7PzLlv+kr;~&u5J=ize1wQdZAZ0jE!n|crW7E6^)EmH0x*}~^%g*95fd;0
zmbPs>CP}*Q=lvp4$ZXrTZQHhO+w%wJn3c8j%+5C3UAFD&%8dBl_qi9D5g8fry}6Ev
z2_Q~)5^N$!IU`BPh1O|=BxQ#*C5*}`lluC515$lxc-vNC)IgW=K|=z7o%cWFpndn=
zX}f{`!VK<lX!FLkOBT(Yy<iOh1h{fQLjvrOQ%*nbfL+k;)$>02_kU+Q5a3m37J;c}
zTzbxteE{GNf?yLt5X=Bzc-mio^Up0nunMCgp*ZJ;%MJvPM3QK)BryP(_v@ei4UvHr
z6<pZ&&fAXylxW0w%M}QG@yfnPZdDXqIP=jPCIit7o$6lTs5icHCgh=N0unBI<zgT*
zptDoN<h$R!7(%ELhKSSOx&nxS==cyKaPf0zAd!_(MC|v$-B2pfoh+ho#bf&+hH7Al
zeEH1*q<}6i9Juxt0<3_LaK_h8L<~fCTg1Svrz2X|nM!={Hli82o)&S!Wq@^;;Sc+N
z{~W{um1qFY-5<^_GW?+R&A}Lmstggwo`S@#F#weeVu1=JFn9vNE-}DWg2+&<6b|VD
zyTX820Y3(!YAs>+sbCifQaOkL6-;5fL8$W($zZ_;CZp305C;~$hhRquZr-r)jjd1z
z31%ZK{-(`P#|Um_Sivn@p$-vz46uqT>QG0B1w9znfS9A8PB2LaHdzA|_)yjXVR*l{
zkcu3@vEf7bxH0nkh`q?8FmoO_Ucui*>_a~P?qQrl<J8L#kWCf%Wh%yn(Y}gU%LfuZ
zDk8@t_|u4e$m`1t<6z}J_rs7?FJ3+<S^J2$5qt6i-|juKZJ|8nXar<dxa}M-+9i7p
zv6dS|d)2&6A)dFrYRGP(%Pv>Z9@+D7%MTpSnztpylXrt5!-k8_QPB?YL8Kx_On8WD
zgT+111d(Op$^$&KLAN5+@?>f7F4~wFi(8TL8+szgVmcMDTp5l&k6~=rA{Dt}!gb^r
zSWY<)M7D|Z2P0cEodj6E42PV>&>DFmQpgt)E-|#sSUU@uKed+F680H@<;-x{p|n<l
zp8yXzqfa@7p%sObAm$9h$>uH4!_mn85rx>wz;0mPi2ZkL#k6;sznu?cXh!T0S>{w6
zL^gvR05NY64l*<+_L>On$rjx9!US;l;LX6@z}yi#2XHh)F@Oo+l)h%fq$v}DNmF2>
zfs^_t0)3N-W<9-N?uedVv{)-J0W5mh#29QM5R5h&KuiRM=0Zvnf#lF=K#WlCgc#9c
zS;qvh(P$<N4^0H>!_a8JwyhI^ZJV2k+B6Z^64?w|1?5gyo6y{}923CRZfYVe1#?F%
z7h2SUiNO3;T#JUOyovSs@@C1GtwipycA=*x5{BpIZ_#GCMuV8XK=x;qCNy{d7?wA~
zC+=vjls;ci&zW=6$H~<UuGL>4^K%v{p}Ab?U%C6Z4p%eC<3ExqU$XR<<U%Vkem)I3
z!`%PIvLz&ze?Zp%vCR@%m16n3hACIF^0#G_T7epA#z)8(rvE?Hg_ap6_uYP)Tb`)h
z2GK)|(Rz!WpFyU@LzjyfQ_<i5^lr&=M4!BSy(W%@)>}LLF67A$Sr20DR_pJ3yeBa~
z^sw{V0FI5;UpwXsScYuhbqGQ`YQ25;6p6W^+tgL&;Ml;>S3CGpSZ>VrTn0m1$y$HU
z&65)I!c?oREz};c=nLCliriqQX->4uivHTgd${GqeAlf*!P^B|jkU|*IdNP(&6C>4
zqOW$)Nw9nvjy^&`?E|gotDV{JmJ9Q~vuhy<<G@fboDAhcI5Dsk#+9Mh1`k6v58TQ6
zTpAxMdW*w$2XjE|dQ{O{2;)nJq6kNrSbdZo9zqeu3!nwqzHn9@9s3%Bu@kHycSZ(x
z2&|bA;|GSCg#oDAgn`0}Ky&~=v%vnTsQAhK3}!@Ul4k5Hs;%f_FcO_g5=04B6;XmB
ziOvB@G`0e&A^~64K@uGVfFy@D!BjmmY#Jg-bQD1l@^zr9M#Q=#5Jcz8rNs%Ao0hm-
z=t_Aq%~%e4bvUtd2FypO;{-_wnmGsNRpExY)16Tgh|VXVky}6f62Ys$1HSxdlSZOT
zCCO8y{_zPY?=~0l+26%7xde3w04W9Q!Is)V1LkBGNde1$zrfW<NfNr*%|ZxRzT^JA
zmcTCY6tLybe{jSY-O=SD&Deu-#rFFZ=3p1N3e^Al)6K5on3B|W0L{#5+PrG&o;8D$
z9VJJ=wm<!FVPYf3<lcP%LC|1@)-Aw}12hTj5D5k>`^C4XIUDt|j4o6rK^e8_(=YqC
zuaR<q<0Od&Y@7Cjug_237^*j7a#aQ~lCq#UYtH7{U_qlC0^1@%Gyuc1|NWO)TNBEm
zMx%@_RIC6AfxhnJIcv(^$)J&PfGr82VdUyLpZ<4xbZB^JxZa4#RXG^p?q<@OkN-Dg
z>6TRVf@tUFHB079o4MBIh{M~4>WwnGgesQH<tZ7VZHqtr^FKfQeBMqw3{0x^1cRqW
zV`%gGwJVk_Syh(=#d=wm^?D<@gsPV0$vs99^0;ZqG|-B^-cjnq$sLjec-e@tEK@9#
zOQ>*3?w(RA%hCZ*7)b!aNV=yOQ%o_Y=<Y6|>Lt0Sl*(9^jfRnC210Om$=y>*o|3z}
zAR&vAdrB#mWoaB0fJSw9xw|Am$fzK>rx-~R#7IFSAwdu_EI|SRfB*yl0w8oX09H^q
zAjl2?0I)v*odGJ40FVGaF&2qJq9Gv`>V>2r0|c`GX8h>CX8eHcOy>S0@<;M3<_6UM
z7yCEpug5NZL!H_0>Hg_HasQ<COXdOkdH!pu@0dU6f8zgSJ>GxR`rY&Z{geOy?N92Z
z{lER^um|$*?*G63*njwc(R?NT)Bei*3jVzR>FWUDb^gKhtL4A=kE_1p-%Fo2`!8M}
z(0AjuCiS;G{?*^1tB-uY%=)SRx&D)pK4u@>f6@KPe3}2j_har$>HqzH;UCR^ssFD0
z<L=e_ckEC4xZ1K8&yCddum>7h+VLO4o@_Yt>>AeaZKUxqyvxWCAjKB>qjQ30UA)#w
z&=RmdwlT`7a8J8Yae=7*c8XL|{@%wA8uvCqfsNX^?UZsS>wX}QD{K}ad4y~iO*p%4
z_cS{u7Ek%?WV6em2(U9#d8(&JDirb^u~7wK4+xP$iiI6IlD|a&S)6o=kG;59N|>K1
zn(0mUqbG3YIY7dQd+*4~)`!S9m7H6HP6YcKHhBc#b%1<GJDT!^vq^Fhq9+GQ)rw<7
zX>L}VIisp%;TckEkcu0>lo@u995$<*Em;XNodjTiCdC%R+TX|_ZR#|1`RR|`^@Teh
zl#w@8fI1FTx2Dy+{blUT{`^kY*V-AZUd?ZZqCS4gW(kY5?retkLbF=>p=59Nl|=sf
zo1Pc|{{N4>5nt#627ylGF`3n>X%`w%bw-Y~zWM_{Si$dc82|=YhISal{N7OY?O`C4
zD|qb}6nLWJ`hUyL+E>-;ricg9J@ZNYP(x(Sct&OI$Y!QWr*=^VN;G3#i>^1n4e#Je
zOVhbFbLpXVu*16enDM+ic;97@R~u&kh__kgP#!R`*rQEnA+_dLkNP~L`0alC|J;c;
zeiK=s8;BsLE)KbG3BD&Br@(Ha@SBT&$?xX`=$;eeel=|R_dIr6-Ro?=HEjnsJ_b`1
zK6Yg^-6;^2aW!xeTK)A~3Rm|L^FCHB_I>jIju7ZGo&N_1*QHkxH2!<tj^laH{Fyx_
z{&=J_f2vo<Z;k$M1Ir~ug1#5Ga52L4CpFf22cxv6fws^ma=KG?212=Y!jNISS!|Lb
z8x-AF@-9``d4}WvRUse6F;u?af>!%@o4iZ?vntS;&zJdPe1dH#04YD93A44o-MpfD
zP{rn_aq>U%RDvC2+bp;xPlsOzauIi3*Lf42`jVKK<K1>ZCRuKdYhi>FDuL<yU&41y
zW;YPPNe&8L>2l=v{$BCN#<T4EqS^BZve&iW4$t~r2^LU29B#Olvb3z==K0V~Xm$T^
zTEZQM|D{j?1st_dU8g^<gdhmv$NdVXjg~&|9i!p3%#sZ~>Q6796s%r-AG$Q^t(3c@
zD?w0UhYr11@feiyl9kY_@H8~|xlmO<8PfQmj1!$@WieW@VxR@Psx<u&LsG06B}sH+
zIY;3Fh+6GQ0@)pP#J1>fe-v9WCi1+f>F4VL?0O~K7T?m4-u|pSkBpUJZZe*16_wAp
zSYZ@;k`3;W3UHKUWc8QeI}0jH5Ly=cGWQPw(Kr2fm=-5L(d`lcXofy8tJY3@Tuadz
zYWXR{mW7XT!RF#RVCe%}=tM*O6!AD3^(!8un~opNI%Uko7$5t@<8+?<JTSi(aPRTt
z&Ml{N#KaBO+?nu~`4Q07^34=s`MzQHq<x4YOM_H9N_$hsJ2<doMH*MCk}b~+4UINa
zTwL7@3kg)_0*#Q$wrCkv#2-q6kYzsssFc?p^mKPeVprz0gBMiUOMbNTyj3-qRER>;
zTxDys(MyyGsUjtSu9$+|_-t!U3fVb1dkK?l`17<+jfl=hrBHnDSV>^R1=TnQeyqbW
z>ov#l%!1|S!1>8UUxIdhQq`_klcHVx0{?#>K3#$4GlXncwldt!g17TcvKq-jo_996
z>oA=tH9CqRl6Yw?Uc`am!V?lHJbizOJaVaScf1U<ZufBT_PTSYy1Kz}Ee^oj{1{Jb
zPK-`C@vPnz@)Il&x&GuPat%?B<3q8e7{hr^F{nmmxEn(YMpk7=cxlRqBY%WZC*EF)
zEGiQ`?WYSV^pF##Wu_nvJYxLapR?xP7cc)>P5e7Dbgabq=b!B~T&_F6?ooU>w%x0A
zH~&MHJ=q`fCH{U<7MDXE4SD32cDZA)WJeWkllJ`UspWaS#eDe^kg^oU_A14UE9<xI
zdNHNo$`_W}M5oe9+e37~{LsytH8U$kdU4k6Wd+jywdyHFMcdx1=~?-!S7R)G4c`N&
zcWK7v+_<;uHA$qDdzdA<PssWlx07Z!S%-(-yIKQguM<#>zG-a^g{xaXf$})Wik>gT
zl#dkzGr(;h0JZDuFn(+k8wNq?PZ5grQ<+sM?wBGt@JnH6v0#or-5wBQWKU~(S_&GT
zkE!tc*ZJ1Y&*p(xX84POb3cClR<n^{&58_5a3*@tLK%RDE@eA8<N0urSMl|?a*z{{
z|I<QGAb!#~MTWAsI&lS{s3^f%*Cq>Md!^qJ#CAZfIepEj-<`VURS_yCz0(?*Ixcj4
z-!zV1_QZhpm=0<;*(nm+F>T=)o?ep@CK5I%g^VAA+RB25ab?7)A~z~egru=I1S|@v
zH7tXV!0wmGS^qj#e+MY;C5eUjEAp$Y?LDkS^QPZ}8WN85?r$u<-Epi;yZ1|J2J`se
z$D6DpH~2F=eI0B&=UFAUnJvZAmClJlK)sutJ?M>xpZiWV&0=G4MZP+x+p>EX=HbCz
zxls%Mw?*u^;LbHWIWCyq+yi)`GmFn9J112CZda_u@YIP%i;srFg_paU02Ifij*7}l
z&CF-<n3E$I?p-QiuZUl*H!IK>(3|>*a|+vbNR`^RP=9G?ymEJ0Z~)d&c*UE$UMepZ
zcITr{0WqhxkjUnM15js_gW=e3Uh|y6ZReaXHIz-=p`x5VvB&rH9y>Amv@^WmXFEw)
zQXYrk3feir=a{jM<dzdx8r#aUNr$FCI&rFeKEw-pN(>Q+wDIkkFnZ$k{sJakHn*?u
za%4b!00ev8NVLM1TY=cl?KB&55BY_MU-sg?c>=Dbz_W{(Z~c?HJi*XpYL)C6Bd8WH
zt+v-#0&o~@t4qESi*)+eW%@VD0|o^yF)n0hM<gQ+PU=IAxwIpk4S6|%K*&)iTbk{k
z!-s&ZD6V|2;CyHbFJb|~GXX8L?gXzy++xyz?IYV^U-~qRctg`i7PNG+E%K%rj1#lA
zgkh1rUqL7W9)e)2c+*k8wKU)vO;P^cyrum5UZ1j=T*)ids=e&KO0D*dbQW0q)Dh%0
zC(oDisK58>E$UtXF$*Lvh}7sso{`|pn*JDIy5^Fm3s$5*zEE=?u5<=l8FJc3r%+H}
zdfoNl2J0^~!-*mOL5o-x32|e0Im*E!yY7F7E5N)W3>+v_LBydlEx?4$RL5f2oYRD#
zaR0wv(-p~wO0eLDl3K=%`{5+0Gd$ktO=W)gWlGZJ0`K<b1^|j#Ha<G@=c9EaeXzf;
z&txt4&rY=X-H_Lvj5eR1K_rweFrPjPyTDZN(Ek|onY%1(4Crv_P7LnIj49do8Wd;~
zCSHo|+D@^-(re?Y49f$%^lZgf&fe1}InrGRWcnc_=giCTrGmDRo?m;MF<+&2oxsg>
z$_RNA=ckrfa;H0KA~dR^p&#0(p-{x$&=IACIfoAR<Nvvu>!za)F-^da-t3#0Dycnp
zwO~NVXwXCl;jE<}>%@<pC<8PLRXJuvO4y>xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r
z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv
zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m
z!GmimlG$=wgXCnr6j@m<1gAL46#T~5<Pf8lV3fF#Ppu>Bnm=2{^@>|t&`9mkEPddj
zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx
z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@<t56P}GC(ea
zcuDo~%vU?vTqAIp#flPNMI2`5;t8&98^HQwmsAsoPNK_fx^Bggb1WVe*N(RqH8#4x
zCQN_rN*!W39u4A)O3%B(eX4-oO~1PlFj1j%A~@Wj>9A|X;;IA>8!oX+y(BKJ$EzV*
znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o
z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE?
zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG)
zaZ7j)dq!O}{?#8Y7~7i6fHh4{`<bqO>pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek
zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m#
z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU
zuSa6v^kXi0x<PZ)*(STjdw~o@Rw-Or2Ax?U!if0^R7qyS!JDYN`R9pBUrvi5{Lgqu
za0I&Zd*A54UPC}|lJz-2f!1VYM+A-ElFU{V`W)8LtCk5Mx|)Il)$QH*gA63%JZFt}
zGq@eFEWXJ~R>Ps?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf
zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q
w)2!4MKjfSAeN<ek%udeE*tim(T?PyxRjZ^lIknLzS6Fbvpe_110000002i;2E&u=k

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000000000000000000000000000000000..aa7d6427e6fa1074b79ccd52ef67ac15c5637e85
GIT binary patch
literal 3844
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
G0001O&w8c+

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000000000000000000000000000000000..9126ae37cbc3587421d6889eadd1d91fbf1994d4
GIT binary patch
literal 7778
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/values/colors.xml b/dataconnect/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..f8c6127d3
--- /dev/null
+++ b/dataconnect/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..8c9ac0b83
--- /dev/null
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Firebase Data Connect</string>
+</resources>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/values/themes.xml b/dataconnect/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..761a1fca9
--- /dev/null
+++ b/dataconnect/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <style name="Theme.FirebaseDataConnect" parent="android:Theme.Material.Light.NoActionBar" />
+</resources>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/xml/backup_rules.xml b/dataconnect/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..fa0f996d2
--- /dev/null
+++ b/dataconnect/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/xml/data_extraction_rules.xml b/dataconnect/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..9ee9997b0
--- /dev/null
+++ b/dataconnect/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt b/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt
new file mode 100644
index 000000000..7c4c4ce12
--- /dev/null
+++ b/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.google.firebase.example.dataconnect
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/build.gradle.kts b/dataconnect/build.gradle.kts
new file mode 100644
index 000000000..f74b04bf2
--- /dev/null
+++ b/dataconnect/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    alias(libs.plugins.android.application) apply false
+    alias(libs.plugins.jetbrains.kotlin.android) apply false
+}
\ No newline at end of file
diff --git a/dataconnect/gradle.properties b/dataconnect/gradle.properties
new file mode 100644
index 000000000..20e2a0152
--- /dev/null
+++ b/dataconnect/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/dataconnect/gradle/wrapper/gradle-wrapper.jar b/dataconnect/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f
GIT binary patch
literal 59203
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
E2O(~oYXATM

literal 0
HcmV?d00001

diff --git a/dataconnect/gradle/wrapper/gradle-wrapper.properties b/dataconnect/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..a048a37a2
--- /dev/null
+++ b/dataconnect/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed May 08 19:29:05 BST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/dataconnect/gradlew b/dataconnect/gradlew
new file mode 100755
index 000000000..4f906e0c8
--- /dev/null
+++ b/dataconnect/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/dataconnect/gradlew.bat b/dataconnect/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/dataconnect/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/dataconnect/settings.gradle.kts b/dataconnect/settings.gradle.kts
new file mode 100644
index 000000000..67a300440
--- /dev/null
+++ b/dataconnect/settings.gradle.kts
@@ -0,0 +1,31 @@
+pluginManagement {
+    repositories {
+        google {
+            content {
+                includeGroupByRegex("com\\.android.*")
+                includeGroupByRegex("com\\.google.*")
+                includeGroupByRegex("androidx.*")
+            }
+        }
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencyResolutionManagement {
+        versionCatalogs {
+            create("libs") {
+                from(files("../gradle/libs.versions.toml"))
+            }
+        }
+    }
+}
+
+rootProject.name = "Firebase Data Connect"
+include(":app")
+ 
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 000000000..e558da6f4
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,31 @@
+[versions]
+agp = "8.4.0"
+kotlin = "1.9.23"
+coreKtx = "1.13.1"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+lifecycleRuntimeKtx = "2.7.0"
+activityCompose = "1.9.0"
+composeBom = "2023.08.00"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/mock-google-services.json b/mock-google-services.json
index 50f138650..a596ae07b 100644
--- a/mock-google-services.json
+++ b/mock-google-services.json
@@ -957,6 +957,25 @@
           "test_interstitial_ad_unit_id": "ca-app-pub-3940256099942544/1033173712"
         }
       }
+    },
+    {
+      "client_info": {
+        "mobilesdk_app_id": "1:474448463284:android:c76572afd2f0ba8d97e8e1",
+        "android_client_info": {
+          "package_name": "com.google.firebase.example.dataconnect"
+        }
+      },
+      "oauth_client": [],
+      "api_key": [
+        {
+          "current_key": "AIzaSyCPndbsEs_QWumL5_B0BpNLuMkvVSecvL0"
+        }
+      ],
+      "services": {
+        "appinvite_service": {
+          "other_platform_oauth_client": []
+        }
+      }
     }
   ],
   "client_info": [],
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b67a2413e..5d0d73ef2 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -13,6 +13,7 @@ include(":admob:app",
         ":config:app",
         ":crash:app",
         ":database:app",
+        ":dataconnect:app",
         ":dynamiclinks:app",
         ":firestore:app",
         ":functions:app",

From 2e242ffa0f8a4620791de4a10edfd439b3875b7c Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 10 May 2024 19:27:15 +0100
Subject: [PATCH 02/63] chore: add Firebase dependencies

---
 dataconnect/app/build.gradle.kts | 6 ++++++
 dataconnect/build.gradle.kts     | 1 +
 gradle/libs.versions.toml        | 7 ++++++-
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index 2463e0da5..65c4bcc79 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -1,6 +1,7 @@
 plugins {
     alias(libs.plugins.android.application)
     alias(libs.plugins.jetbrains.kotlin.android)
+    alias(libs.plugins.google.services)
 }
 
 android {
@@ -59,6 +60,11 @@ dependencies {
     implementation(libs.androidx.ui.graphics)
     implementation(libs.androidx.ui.tooling.preview)
     implementation(libs.androidx.material3)
+
+    // Firebase dependencies
+    implementation(libs.firebase.auth)
+    implementation(libs.firebase.dataconnect)
+
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.androidx.espresso.core)
diff --git a/dataconnect/build.gradle.kts b/dataconnect/build.gradle.kts
index f74b04bf2..7252638e5 100644
--- a/dataconnect/build.gradle.kts
+++ b/dataconnect/build.gradle.kts
@@ -2,4 +2,5 @@
 plugins {
     alias(libs.plugins.android.application) apply false
     alias(libs.plugins.jetbrains.kotlin.android) apply false
+    alias(libs.plugins.google.services) apply false
 }
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e558da6f4..3df2050c2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,7 @@
 [versions]
 agp = "8.4.0"
+firebaseAuth = "23.0.0"
+firebaseDataConnect = "16.0.0-alpha01"
 kotlin = "1.9.23"
 coreKtx = "1.13.1"
 junit = "4.13.2"
@@ -8,9 +10,12 @@ espressoCore = "3.5.1"
 lifecycleRuntimeKtx = "2.7.0"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
+googleServices = "4.4.1"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+firebase-auth = { module = "com.google.firebase:firebase-auth", version.ref = "firebaseAuth" }
+firebase-dataconnect = { module = "com.google.firebase:firebase-dataconnect", version.ref = "firebaseDataConnect" }
 junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
 androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -28,4 +33,4 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }
 jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-
+google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }

From bc89f0b4f9d439b55095f35d953e2e8078a7d2bb Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 13 May 2024 21:59:35 +0100
Subject: [PATCH 03/63] add first schema

---
 dataconnect/app/build.gradle.kts              |  3 +
 dataconnect/dataconnect.yaml                  |  7 ++
 .../dataconnect/connectors/connector.yaml     |  6 ++
 dataconnect/schema/movie_schema.gql           | 70 +++++++++++++++++++
 4 files changed, 86 insertions(+)
 create mode 100644 dataconnect/dataconnect.yaml
 create mode 100644 dataconnect/dataconnect/connectors/connector.yaml
 create mode 100644 dataconnect/schema/movie_schema.gql

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index 65c4bcc79..c55e39a3e 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -48,6 +48,9 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
+    sourceSets.getByName("main") {
+        java.srcDirs("build/generated/sources")
+    }
 }
 
 dependencies {
diff --git a/dataconnect/dataconnect.yaml b/dataconnect/dataconnect.yaml
new file mode 100644
index 000000000..ccd5417c5
--- /dev/null
+++ b/dataconnect/dataconnect.yaml
@@ -0,0 +1,7 @@
+specVersion: v1alpha
+serviceId: local
+schema:
+  source: /schema
+connectorDirs: [
+  "./dataconnect/connectors",
+]
diff --git a/dataconnect/dataconnect/connectors/connector.yaml b/dataconnect/dataconnect/connectors/connector.yaml
new file mode 100644
index 000000000..5f73ab105
--- /dev/null
+++ b/dataconnect/dataconnect/connectors/connector.yaml
@@ -0,0 +1,6 @@
+connectorId: movies
+authMode: PUBLIC
+generate:
+  kotlinSdk:
+    outputDir: ../../app/build/generated/sources/com/google/firebase/dataconnect/movies
+    package: com.google.firebase.dataconnect.movies
diff --git a/dataconnect/schema/movie_schema.gql b/dataconnect/schema/movie_schema.gql
new file mode 100644
index 000000000..b77bcc079
--- /dev/null
+++ b/dataconnect/schema/movie_schema.gql
@@ -0,0 +1,70 @@
+# Movies
+type Movie
+  @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
+  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
+  title: String!
+  releaseYear: Int @col(name: "release_year")
+  genre: String
+  rating: Int @col(name: "rating")
+}
+# Movie Metadata
+# Movie - MovieMetadata is a one-to-one relationship
+type MovieMetadata
+  @table(
+    name: "MovieMetadata"
+  ) {
+  # @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
+  # In this case, @ref(fields: "id") is implied
+  movie: Movie! @ref
+  # movieId: UUID <- this is created by the above @ref
+  director: String @col(name: "director")
+}
+# Actors
+# Suppose an actor can participate in multiple movies and movies can have multiple actors
+# Movie - Actors (or vice versa) is a many to many relationship
+type Actor @table(name: "Actors", singular: "actor", plural: "actors") {
+  id: UUID! @col(name: "actor_id") @default(expr: "uuidV4()")
+  name: String! @col(name: "name", dataType: "varchar(30)")
+}
+# Join table for many-to-many relationship for movies and actors
+# The 'key' param signifies the primary key(s) of this table
+# In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
+type MovieActor @table(key: ["movie", "actor"]) {
+  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
+  # In this case, @ref(fields: "id") is implied
+  movie: Movie! @ref
+  # movieId: UUID! <- this is created by the above @ref, see: implicit.gql
+  actor: Actor! @ref
+  # actorId: UUID! <- this is created by the above @ref, see: implicit.gql
+  role: String! @col(name: "role") # "main" or "supporting"
+  # TODO: optional other fields
+}
+# Users
+# Suppose a user can leave reviews for movies
+# user:reviews is a one to many relationship, movie:reviews is a one to many relationship, movie:user is a many to many relationship
+type User
+  @table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
+  id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
+  auth: String @col(name: "user_auth") @default(expr: "auth.uid")
+  username: String! @col(name: "username", dataType: "varchar(30)")
+  # The following are generated from the @ref in the Review table
+  # reviews_on_user
+  # movies_via_Review
+}
+# Reviews
+type Review @table(name: "Reviews", key: ["movie", "user"]) {
+  id: UUID! @col(name: "review_id") @default(expr: "uuidV4()")
+  user: User!
+  movie: Movie!
+  rating: Int
+  reviewText: String
+  reviewDate: Date! @default(expr: "request.time")
+}
+# Self Joins
+extend type Movie {
+    sequelTo: Movie
+}
+
+type Hello @table {
+    id: UUID!
+}
\ No newline at end of file

From 441033fd7c4dfb48f7f662d434a3f1a84bb3d5ac Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 13 May 2024 23:33:46 +0100
Subject: [PATCH 04/63] update with new schema

---
 .../example/dataconnect/MoviesViewModel.kt    |  47 ++
 dataconnect/dataconnect.yaml                  |   7 -
 .../dataconnect/connectors/connector.yaml     |  11 +-
 .../dataconnect/connectors/mutations.gql      | 113 ++++
 .../dataconnect/connectors/queries.gql        | 565 ++++++++++++++++++
 dataconnect/dataconnect/dataconnect.yaml      |  10 +
 .../schema/schema.gql}                        |  53 +-
 7 files changed, 787 insertions(+), 19 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
 delete mode 100644 dataconnect/dataconnect.yaml
 create mode 100644 dataconnect/dataconnect/connectors/mutations.gql
 create mode 100644 dataconnect/dataconnect/connectors/queries.gql
 create mode 100644 dataconnect/dataconnect/dataconnect.yaml
 rename dataconnect/{schema/movie_schema.gql => dataconnect/schema/schema.gql} (67%)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
new file mode 100644
index 000000000..762b323d7
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
@@ -0,0 +1,47 @@
+package com.google.firebase.example.dataconnect
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.flow
+import com.google.firebase.dataconnect.movies.instance
+import kotlinx.coroutines.launch
+
+class MoviesViewModel : ViewModel() {
+
+    fun fetchMovies() {
+        viewModelScope.launch {
+            val connector = MoviesConnector.instance
+
+            connector.listMovies.execute()
+            connector.createMovie.execute(
+                title = "Empire Strikes Back",
+                releaseYear = 1980,
+                genre = "Sci-Fi",
+                rating = 5
+            )
+
+            connector.listMoviesByGenre.execute(genre = "Sci-Fi")
+
+//            connector.addMovie.execute(title = "", genre = "")
+//
+//            val result = connector.listMovies.execute()
+//            result.data.movies.firstOrNull()
+
+//            connector.listMoviesGenre.flow(genre = "")
+//                .collect { data ->
+//                    val movies = data.movies
+//                }
+
+            // Connect to the emulator on "10.0.2.2:9510"
+            connector.dataConnect.useEmulator()
+
+// (alternatively) if you're running your emulator on non-default port:
+            connector.dataConnect.useEmulator(
+                port = 9999
+            )
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/dataconnect.yaml b/dataconnect/dataconnect.yaml
deleted file mode 100644
index ccd5417c5..000000000
--- a/dataconnect/dataconnect.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-specVersion: v1alpha
-serviceId: local
-schema:
-  source: /schema
-connectorDirs: [
-  "./dataconnect/connectors",
-]
diff --git a/dataconnect/dataconnect/connectors/connector.yaml b/dataconnect/dataconnect/connectors/connector.yaml
index 5f73ab105..5e80be132 100644
--- a/dataconnect/dataconnect/connectors/connector.yaml
+++ b/dataconnect/dataconnect/connectors/connector.yaml
@@ -1,6 +1,13 @@
-connectorId: movies
+connectorId: movie-connector
+# Required. Accepted values are either "PUBLIC" or "ADMIN" (only "PUBLIC" for gated private
+# preview). If "ADMIN", the connector in this directory is an AdminConnector and its operations
+# are gated by IAM.
 authMode: PUBLIC
 generate:
+  # (Web SDK generation omitted, but can be found in https://github.com/firebase/quickstart-js)
   kotlinSdk:
-    outputDir: ../../app/build/generated/sources/com/google/firebase/dataconnect/movies
+    # Create a custom package name for your generated SDK
     package: com.google.firebase.dataconnect.movies
+    # Specify where to store the generated SDK
+    # We're using the build/ directory so that generated code doesn't get checked into git
+    outputDir: ../../app/build/generated/sources/com/google/firebase/dataconnect/movies
diff --git a/dataconnect/dataconnect/connectors/mutations.gql b/dataconnect/dataconnect/connectors/mutations.gql
new file mode 100644
index 000000000..324c223d8
--- /dev/null
+++ b/dataconnect/dataconnect/connectors/mutations.gql
@@ -0,0 +1,113 @@
+# Create a movie based on user input
+mutation createMovie(
+  $title: String!
+  $releaseYear: Int!
+  $genre: String!
+  $rating: Float
+  $description: String
+  $imageUrl: String!
+  $tags: [String!] = []
+) {
+  movie_insert(
+    data: {
+      title: $title
+      releaseYear: $releaseYear
+      genre: $genre
+      rating: $rating
+      description: $description
+      imageUrl: $imageUrl
+      tags: $tags
+    }
+  )
+}
+
+# Update movie information based on the provided ID
+mutation updateMovie(
+  $id: UUID!
+  $title: String
+  $releaseYear: Int
+  $genre: String
+  $rating: Float
+  $description: String
+  $imageUrl: String
+  $tags: [String!] = []
+) {
+  movie_update(
+    id: $id
+    data: {
+      title: $title
+      releaseYear: $releaseYear
+      genre: $genre
+      rating: $rating
+      description: $description
+      imageUrl: $imageUrl
+      tags: $tags
+    }
+  )
+}
+
+# Delete a movie by its ID
+mutation deleteMovie($id: UUID!) {
+  movie_delete(id: $id)
+}
+
+# Delete movies with a rating lower than the specified minimum rating
+mutation deleteUnpopularMovies($minRating: Float!) {
+  movie_deleteMany(where: { rating: { le: $minRating } })
+}
+
+# Add a movie to the user's watched list
+mutation addWatchedMovie($movieId: UUID!) @auth(level: USER) {
+  watchedMovie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
+}
+
+# Remove a movie from the user's watched list
+mutation deleteWatchedMovie($userId: String!, $movieId: UUID!) @auth(level: USER) {
+  watchedMovie_delete(key: { userId: $userId, movieId: $movieId })
+}
+
+# Add a movie to the user's favorites list
+mutation addFavoritedMovie($movieId: UUID!) @auth(level: USER) {
+  favoriteMovie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
+}
+
+# Remove a movie from the user's favorites list
+mutation deleteFavoritedMovie($userId: String!, $movieId: UUID!) @auth(level: USER) {
+  favoriteMovie_delete(key: { userId: $userId, movieId: $movieId })
+}
+
+# Add an actor to the user's favorites list
+mutation addFavoritedActor($actorId: UUID!) @auth(level: USER) {
+  favoriteActor_upsert(data: { userId_expr: "auth.uid", actorId: $actorId })
+}
+
+# Remove an actor from the user's favorites list
+mutation deleteFavoriteActor($userId: String!, $actorId: UUID!) @auth(level: USER) {
+  favoriteActor_delete(key: { userId: $userId, actorId: $actorId })
+}
+
+# Add a review for a movie
+mutation addReview($movieId: UUID!, $rating: Int!, $reviewText: String!) @auth(level: USER) {
+  review_upsert(
+    data: {
+      userId_expr: "auth.uid"
+      movieId: $movieId
+      rating: $rating
+      reviewText: $reviewText
+      reviewDate_date: { today: true }
+    }
+  )
+}
+
+# Delete a user's review for a movie
+mutation deleteReview($movieId: UUID!, $userId: String!) @auth(level: USER) {
+  review_delete(key: { userId: $userId, movieId: $movieId })
+}
+
+# Upsert (update or insert) a user based on their username
+mutation upsertUser($username: String!) @auth(level: USER) {
+  user_upsert(data: {
+    id_expr: "auth.uid",
+    username: $username
+  })
+}
\ No newline at end of file
diff --git a/dataconnect/dataconnect/connectors/queries.gql b/dataconnect/dataconnect/connectors/queries.gql
new file mode 100644
index 000000000..8f8653101
--- /dev/null
+++ b/dataconnect/dataconnect/connectors/queries.gql
@@ -0,0 +1,565 @@
+# List subset of fields for movies
+query ListMovies @auth(level: PUBLIC) {
+  movies {
+    id
+    title
+    imageUrl
+    releaseYear
+    genre
+    rating
+    tags
+  }
+}
+
+# List subset of fields for users
+query ListUsers @auth(level: PUBLIC) {
+  users {
+    id
+    username
+    favoriteActors_on_user {
+      actor {
+        id
+        name
+        imageUrl
+      }
+    }
+    favoriteMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+        tags
+      }
+    }
+    reviews_on_user {
+      id
+      rating
+      reviewText
+      reviewDate
+      movie {
+        id
+        title
+      }
+    }
+    watchedMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+      }
+    }
+  }
+}
+
+# List movies of a certain genre
+query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC) {
+  mostPopular: movies(
+    where: { genre: { eq: $genre } }
+    orderBy: { rating: DESC }
+  ) {
+    id
+    title
+    imageUrl
+    rating
+    tags
+  }
+  mostRecent: movies(
+    where: { genre: { eq: $genre } }
+    orderBy: { releaseYear: DESC }
+  ) {
+    id
+    title
+    imageUrl
+    rating
+    tags
+  }
+}
+
+# List movies by the order of release
+query ListMoviesByReleaseYear @auth(level: PUBLIC) {
+  movies(orderBy: [{ releaseYear: DESC }]) {
+    id
+    title
+    imageUrl
+  }
+}
+
+# Get movie by id
+query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
+  movie(id: $id) {
+    id
+    title
+    imageUrl
+    releaseYear
+    genre
+    rating
+    description
+    tags
+    metadata: movieMetadatas_on_movie {
+      director
+    }
+    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
+      id
+      name
+      imageUrl
+    }
+    supportingActors: actors_via_MovieActor(
+      where: { role: { eq: "supporting" } }
+    ) {
+      id
+      name
+      imageUrl
+    }
+    sequelTo {
+      id
+      title
+      imageUrl
+    }
+    reviews: reviews_on_movie {
+      id
+      reviewText
+      reviewDate
+      rating
+      user {
+        id
+        username
+      }
+    }
+  }
+}
+
+# Get actor by id
+query GetActorById($id: UUID!) @auth(level: PUBLIC) {
+  actor(id: $id) {
+    id
+    name
+    imageUrl
+    biography
+    mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) {
+      id
+      title
+      genre
+      tags
+      imageUrl
+    }
+    supportingActors: movies_via_MovieActor(
+      where: { role: { eq: "supporting" } }
+    ) {
+      id
+      title
+      genre
+      tags
+      imageUrl
+    }
+  }
+}
+
+# User movie preferences
+query UserMoviePreferences($username: String!) @auth(level: USER) {
+  users(where: { username: { eq: $username } }) {
+    likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
+      title
+      imageUrl
+      genre
+      description
+    }
+    dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
+      title
+      imageUrl
+      genre
+      description
+    }
+  }
+}
+
+# Get movie metadata
+query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
+  movie(id: $id) {
+    movieMetadatas_on_movie {
+      director
+    }
+  }
+}
+
+# Get movie cast and actor roles
+query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
+  movie(id: $movieId) {
+    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
+      id
+      name
+      imageUrl
+    }
+    supportingActors: actors_via_MovieActor(
+      where: { role: { eq: "supporting" } }
+    ) {
+      id
+      name
+      imageUrl
+    }
+  }
+  actor(id: $actorId) {
+    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
+      id
+      title
+      imageUrl
+    }
+    supportingRoles: movies_via_MovieActor(
+      where: { role: { eq: "supporting" } }
+    ) {
+      id
+      title
+      imageUrl
+    }
+  }
+}
+
+# List movies by partial title match
+query ListMoviesByPartialTitle($input: String!) @auth(level: PUBLIC) {
+  movies(where: { title: { contains: $input } }) {
+    id
+    title
+    genre
+    rating
+    imageUrl
+  }
+}
+
+# Fetch a single movie using key scalars (same as get movie by id)
+query MovieByKey($key: Movie_Key!) @auth(level: PUBLIC) {
+  movie(key: $key) {
+    title
+    imageUrl
+  }
+}
+
+# Fetch movies by title
+query MovieByTitle($title: String!) @auth(level: PUBLIC) {
+  movies(where: { title: { eq: $title } }) {
+    id
+    title
+    imageUrl
+    genre
+    rating
+  }
+}
+
+# Fetch top-rated movies by genre
+query MovieByTopRating($genre: String) @auth(level: PUBLIC) {
+  mostPopular: movies(
+    where: { genre: { eq: $genre } }
+    orderBy: { rating: DESC }
+  ) {
+    id
+    title
+    imageUrl
+    rating
+    tags
+  }
+}
+
+# List movies by tag
+query ListMoviesByTag($tag: String!) @auth(level: PUBLIC) {
+  movies(where: { tags: { includes: $tag } }) {
+    id
+    title
+    imageUrl
+    genre
+    rating
+  }
+}
+
+# List top 10 movies
+query MoviesTop10 @auth(level: PUBLIC) {
+  movies(orderBy: [{ rating: DESC }], limit: 10) {
+    id
+    title
+    imageUrl
+    rating
+    genre
+    tags
+    metadata: movieMetadatas_on_movie {
+      director
+    }
+    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
+      id
+      name
+      imageUrl
+    }
+    supportingActors: actors_via_MovieActor(where: { role: { eq: "supporting" } }) {
+      id
+      name
+      imageUrl
+    }
+  }
+}
+
+# List movies by release year range
+query MoviesByReleaseYear($min: Int, $max: Int) @auth(level: PUBLIC) {
+  movies(
+    where: { releaseYear: { le: $max, ge: $min } }
+    orderBy: [{ releaseYear: ASC }]
+  ) {
+    id
+    rating
+    title
+    imageUrl
+  }
+}
+
+# List recently released movies
+query MoviesRecentlyReleased @auth(level: PUBLIC) {
+  movies(where: { releaseYear: { ge: 2010 } }) {
+    id
+    title
+    rating
+    imageUrl
+    genre
+    tags
+  }
+}
+
+# List movies with filtering on fields
+query ListMoviesFilter($genre: String, $limit: Int) @auth(level: PUBLIC) {
+  movies(where: { genre: { eq: $genre } }, limit: $limit) {
+    title
+    imageUrl
+  }
+}
+
+# List movies by partial title string match
+query ListMoviesByTitleString(
+  $prefix: String
+  $suffix: String
+  $contained: String
+) @auth(level: PUBLIC) {
+  prefixed: movies(where: { description: { startsWith: $prefix } }) {
+    title
+  }
+  suffixed: movies(where: { description: { endsWith: $suffix } }) {
+    title
+  }
+  contained: movies(where: { description: { contains: $contained } }) {
+    title
+  }
+}
+
+# List movies by rating and genre with OR/AND filters
+query ListMoviesByRatingAndGenre($minRating: Float!, $genre: String)
+@auth(level: PUBLIC) {
+  movies(
+    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
+  ) {
+    title
+    imageUrl
+  }
+}
+
+# Get favorite movies by user ID
+query GetFavoriteMoviesById($id: String!) @auth(level: USER) {
+  user(id: $id) {
+    favoriteMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+        releaseYear
+        rating
+        description
+      }
+    }
+  }
+}
+
+# Get favorite actors by user ID
+query GetFavoriteActorsById($id: String!) @auth(level: USER) {
+  user(id: $id) {
+    favoriteActors_on_user {
+      actor {
+        id
+        name
+        imageUrl
+      }
+    }
+  }
+}
+
+# Get watched movies by user ID
+query GetWatchedMoviesByAuthId($id: String!) @auth(level: USER) {
+  user(id: $id) {
+    watchedMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+        releaseYear
+        rating
+        description
+      }
+    }
+  }
+}
+
+# Get user by ID
+query GetUserById($id: String!) @auth(level: USER) {
+  user(id: $id) {
+    id
+    username
+    reviews: reviews_on_user {
+      id
+      rating
+      reviewDate
+      reviewText
+      movie {
+        id
+        title
+      }
+    }
+    watched: watchedMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+        releaseYear
+        rating
+        description
+        tags
+        metadata: movieMetadatas_on_movie {
+          director
+        }
+      }
+    }
+    favoriteMovies: favoriteMovies_on_user {
+      movie {
+        id
+        title
+        genre
+        imageUrl
+        releaseYear
+        rating
+        description
+        tags
+        metadata: movieMetadatas_on_movie {
+          director
+        }
+      }
+    }
+    favoriteActors: favoriteActors_on_user {
+      actor {
+        id
+        name
+        imageUrl
+      }
+    }
+  }
+}
+
+# Check if a movie is watched by user
+query GetIfWatched($id: String!, $movieId: UUID!) @auth(level: USER) {
+  watchedMovie(key: { userId: $id, movieId: $movieId }) {
+    movieId
+  }
+}
+
+# Check if a movie is favorited by user
+query GetIfFavoritedMovie($id: String!, $movieId: UUID!) @auth(level: USER) {
+  favoriteMovie(key: { userId: $id, movieId: $movieId }) {
+    movieId
+  }
+}
+
+# Check if an actor is favorited by user
+query GetIfFavoritedActor($id: String!, $actorId: UUID!) @auth(level: USER) {
+  favoriteActor(key: { userId: $id, actorId: $actorId }) {
+    actorId
+  }
+}
+
+# Fuzzy search for movies, actors, and reviews
+query fuzzySearch(
+  $input: String
+  $minYear: Int!
+  $maxYear: Int!
+  $minRating: Float!
+  $maxRating: Float!
+  $genre: String!
+) @auth(level: PUBLIC) {
+  moviesMatchingTitle: movies(
+    where: {
+      _and: [
+        { releaseYear: { ge: $minYear } }
+        { releaseYear: { le: $maxYear } }
+        { rating: { ge: $minRating } }
+        { rating: { le: $maxRating } }
+        { genre: { contains: $genre } }
+        { title: { contains: $input } }
+      ]
+    }
+  ) {
+    id
+    title
+    genre
+    rating
+    imageUrl
+  }
+  moviesMatchingDescription: movies(
+    where: {
+      _and: [
+        { releaseYear: { ge: $minYear } }
+        { releaseYear: { le: $maxYear } }
+        { rating: { ge: $minRating } }
+        { rating: { le: $maxRating } }
+        { genre: { contains: $genre } }
+        { description: { contains: $input } }
+      ]
+    }
+  ) {
+    id
+    title
+    genre
+    rating
+    imageUrl
+  }
+  actorsMatchingName: actors(where: { name: { contains: $input } }) {
+    id
+    name
+    imageUrl
+  }
+  reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) {
+    id
+    rating
+    reviewText
+    reviewDate
+    movie {
+      id
+      title
+    }
+    user {
+      id
+      username
+    }
+  }
+}
+
+# Search movie descriptions using L2 similarity with Vertex AI
+query searchMovieDescriptionUsingL2Similarity($query: String!)
+@auth(level: PUBLIC) {
+  movies_descriptionEmbedding_similarity(
+    compare_embed: { model: "textembedding-gecko@001", text: $query }
+    method: L2
+    within: 2
+    where: { description: { ne: "" } }
+    limit: 5
+  ) {
+    id
+    title
+    description
+    tags
+    rating
+    imageUrl
+  }
+}
\ No newline at end of file
diff --git a/dataconnect/dataconnect/dataconnect.yaml b/dataconnect/dataconnect/dataconnect.yaml
new file mode 100644
index 000000000..5d09e5a65
--- /dev/null
+++ b/dataconnect/dataconnect/dataconnect.yaml
@@ -0,0 +1,10 @@
+specVersion: "v1alpha"
+serviceId: "dataconnect"
+schema:
+  source: "./schema"
+  datasource:
+    postgresql:
+      database: "postgres"
+      cloudSql:
+        instanceId: "cloud-sql-instance"
+connectorDirs: ["./connectors"]
diff --git a/dataconnect/schema/movie_schema.gql b/dataconnect/dataconnect/schema/schema.gql
similarity index 67%
rename from dataconnect/schema/movie_schema.gql
rename to dataconnect/dataconnect/schema/schema.gql
index b77bcc079..1c7f04c76 100644
--- a/dataconnect/schema/movie_schema.gql
+++ b/dataconnect/dataconnect/schema/schema.gql
@@ -3,10 +3,16 @@ type Movie
   @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
   id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
   title: String!
+  imageUrl: String! @col(name: "image_url")
   releaseYear: Int @col(name: "release_year")
   genre: String
-  rating: Int @col(name: "rating")
+  rating: Float @col(name: "rating")
+  description: String @col(name: "description")
+  tags: [String] @col(name: "tags")
+  # Vectors
+  descriptionEmbedding: Vector @col(size:768) # vector
 }
+
 # Movie Metadata
 # Movie - MovieMetadata is a one-to-one relationship
 type MovieMetadata
@@ -18,14 +24,19 @@ type MovieMetadata
   movie: Movie! @ref
   # movieId: UUID <- this is created by the above @ref
   director: String @col(name: "director")
+  # TODO: optional other fields
 }
+
 # Actors
 # Suppose an actor can participate in multiple movies and movies can have multiple actors
 # Movie - Actors (or vice versa) is a many to many relationship
 type Actor @table(name: "Actors", singular: "actor", plural: "actors") {
   id: UUID! @col(name: "actor_id") @default(expr: "uuidV4()")
+  imageUrl: String! @col(name: "image_url")
   name: String! @col(name: "name", dataType: "varchar(30)")
+  biography: String @col(name: "biography")
 }
+
 # Join table for many-to-many relationship for movies and actors
 # The 'key' param signifies the primary key(s) of this table
 # In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
@@ -34,37 +45,59 @@ type MovieActor @table(key: ["movie", "actor"]) {
   # In this case, @ref(fields: "id") is implied
   movie: Movie! @ref
   # movieId: UUID! <- this is created by the above @ref, see: implicit.gql
+
   actor: Actor! @ref
   # actorId: UUID! <- this is created by the above @ref, see: implicit.gql
+
   role: String! @col(name: "role") # "main" or "supporting"
   # TODO: optional other fields
 }
+
 # Users
 # Suppose a user can leave reviews for movies
 # user:reviews is a one to many relationship, movie:reviews is a one to many relationship, movie:user is a many to many relationship
 type User
   @table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
-  id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
-  auth: String @col(name: "user_auth") @default(expr: "auth.uid")
-  username: String! @col(name: "username", dataType: "varchar(30)")
+  # id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
+  id: String! @col(name: "user_auth")
+  username: String! @col(name: "username", dataType: "varchar(50)")
   # The following are generated from the @ref in the Review table
   # reviews_on_user
   # movies_via_Review
 }
+
+# Join table for many-to-many relationship for users and favorite movies
+type FavoriteMovie
+  @table(name: "FavoriteMovies", key: ["user", "movie"]) {
+  user: User! @ref
+  movie: Movie! @ref
+}
+
+# Join table for many-to-many relationship for users and favorite actors
+type FavoriteActor
+  @table(name: "FavoriteActors", key: ["user", "actor"]) {
+  user: User! @ref
+  actor: Actor! @ref
+}
+
+# Join table for many-to-many relationship for users and watched movies
+type WatchedMovie
+  @table(name: "WatchedMovies", key: ["user", "movie"]) {
+  user: User! @ref
+  movie: Movie! @ref
+}
+
 # Reviews
 type Review @table(name: "Reviews", key: ["movie", "user"]) {
   id: UUID! @col(name: "review_id") @default(expr: "uuidV4()")
-  user: User!
-  movie: Movie!
+  user: User! @ref
+  movie: Movie! @ref
   rating: Int
   reviewText: String
   reviewDate: Date! @default(expr: "request.time")
 }
+
 # Self Joins
 extend type Movie {
     sequelTo: Movie
 }
-
-type Hello @table {
-    id: UUID!
-}
\ No newline at end of file

From 3c999166d847b3ffe9ab8b67d582d899cc2b83d0 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 31 May 2024 20:27:05 +0100
Subject: [PATCH 05/63] add kotlin serialization and androidx.lifecycle

---
 dataconnect/app/build.gradle.kts | 2 ++
 gradle/libs.versions.toml        | 8 +++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index c55e39a3e..534392c48 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -1,6 +1,7 @@
 plugins {
     alias(libs.plugins.android.application)
     alias(libs.plugins.jetbrains.kotlin.android)
+    alias(libs.plugins.kotlin.serialization)
     alias(libs.plugins.google.services)
 }
 
@@ -57,6 +58,7 @@ dependencies {
 
     implementation(libs.androidx.core.ktx)
     implementation(libs.androidx.lifecycle.runtime.ktx)
+    implementation(libs.androidx.lifecycle.viewmodel.android)
     implementation(libs.androidx.activity.compose)
     implementation(platform(libs.androidx.compose.bom))
     implementation(libs.androidx.ui)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3df2050c2..bed607ba5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,25 +1,26 @@
 [versions]
 agp = "8.4.0"
 firebaseAuth = "23.0.0"
-firebaseDataConnect = "16.0.0-alpha01"
+firebaseDataConnect = "16.0.0-alpha03"
 kotlin = "1.9.23"
 coreKtx = "1.13.1"
 junit = "4.13.2"
 junitVersion = "1.1.5"
 espressoCore = "3.5.1"
-lifecycleRuntimeKtx = "2.7.0"
+lifecycle = "2.8.1"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
 googleServices = "4.4.1"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-lifecycle-viewmodel-android = { module = "androidx.lifecycle:lifecycle-viewmodel-android", version.ref = "lifecycle" }
 firebase-auth = { module = "com.google.firebase:firebase-auth", version.ref = "firebaseAuth" }
 firebase-dataconnect = { module = "com.google.firebase:firebase-dataconnect", version.ref = "firebaseDataConnect" }
 junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
 androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
-androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
 androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
 androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
 androidx-ui = { group = "androidx.compose.ui", name = "ui" }
@@ -33,4 +34,5 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }
 jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
 google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }

From 44a274d505d84e09332c0be01271d8d74b8e5912 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 31 May 2024 20:27:31 +0100
Subject: [PATCH 06/63] firebase init

---
 dataconnect/.gitignore                            | 2 ++
 dataconnect/dataconnect/connectors/connector.yaml | 2 +-
 dataconnect/firebase.json                         | 6 ++++++
 3 files changed, 9 insertions(+), 1 deletion(-)
 create mode 100644 dataconnect/firebase.json

diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore
index aa724b770..5b4ab9b06 100644
--- a/dataconnect/.gitignore
+++ b/dataconnect/.gitignore
@@ -13,3 +13,5 @@
 .externalNativeBuild
 .cxx
 local.properties
+.dataconnect/
+.firebaserc
diff --git a/dataconnect/dataconnect/connectors/connector.yaml b/dataconnect/dataconnect/connectors/connector.yaml
index 5e80be132..2ba51749d 100644
--- a/dataconnect/dataconnect/connectors/connector.yaml
+++ b/dataconnect/dataconnect/connectors/connector.yaml
@@ -1,4 +1,4 @@
-connectorId: movie-connector
+connectorId: movies
 # Required. Accepted values are either "PUBLIC" or "ADMIN" (only "PUBLIC" for gated private
 # preview). If "ADMIN", the connector in this directory is an AdminConnector and its operations
 # are gated by IAM.
diff --git a/dataconnect/firebase.json b/dataconnect/firebase.json
new file mode 100644
index 000000000..fe0eb6bfc
--- /dev/null
+++ b/dataconnect/firebase.json
@@ -0,0 +1,6 @@
+{
+  "dataconnect": {
+    "source": "dataconnect",
+    "location": "us-central1"
+  }
+}

From 08a034c5cdcdb09f88530af0412e8ec3f427ceee Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 3 Jun 2024 03:50:17 +0100
Subject: [PATCH 07/63] setup bottom navigation

---
 dataconnect/app/build.gradle.kts              |   1 +
 .../example/dataconnect/MainActivity.kt       | 136 +++++++++++++++---
 .../example/dataconnect/MoviesViewModel.kt    |  47 ------
 .../dataconnect/feature/genres/Navigation.kt  |  21 +++
 .../dataconnect/feature/movies/Navigation.kt  |  21 +++
 .../dataconnect/feature/profile/Navigation.kt |  21 +++
 .../dataconnect/feature/search/Navigation.kt  |  21 +++
 .../app/src/main/res/values/strings.xml       |   6 +
 gradle/libs.versions.toml                     |   2 +
 9 files changed, 206 insertions(+), 70 deletions(-)
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/search/Navigation.kt

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index 534392c48..db4b72919 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -65,6 +65,7 @@ dependencies {
     implementation(libs.androidx.ui.graphics)
     implementation(libs.androidx.ui.tooling.preview)
     implementation(libs.androidx.material3)
+    implementation(libs.compose.navigation)
 
     // Firebase dependencies
     implementation(libs.firebase.auth)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index 03e2481f7..4996cbe10 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -1,17 +1,47 @@
 package com.google.firebase.example.dataconnect
 
 import android.os.Bundle
+import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
+import com.google.firebase.example.dataconnect.feature.genres.genresScreen
+import com.google.firebase.example.dataconnect.feature.movies.MOVIES_ROUTE
+import com.google.firebase.example.dataconnect.feature.movies.moviesScreen
+import com.google.firebase.example.dataconnect.feature.movies.navigateToMovies
+import com.google.firebase.example.dataconnect.feature.profile.PROFILE_ROUTE
+import com.google.firebase.example.dataconnect.feature.profile.profileScreen
+import com.google.firebase.example.dataconnect.feature.search.SEARCH_ROUTE
+import com.google.firebase.example.dataconnect.feature.search.searchScreen
 import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 
 class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -19,29 +49,89 @@ class MainActivity : ComponentActivity() {
         enableEdgeToEdge()
         setContent {
             FirebaseDataConnectTheme {
-                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
-                    Greeting(
-                        name = "Android",
-                        modifier = Modifier.padding(innerPadding)
-                    )
+                val navController = rememberNavController()
+                Scaffold(
+                    modifier = Modifier.fillMaxSize(),
+                    bottomBar = {
+                        NavigationBar {
+                            val navBackStackEntry by navController.currentBackStackEntryAsState()
+                            val currentDestination = navBackStackEntry?.destination
+                            NavigationBarItem(
+                                icon = { Icon(Icons.Filled.Home, contentDescription = null) },
+                                label = { Text(stringResource(R.string.label_movies)) },
+                                selected = isRouteSelected(currentDestination, MOVIES_ROUTE),
+                                onClick = {
+                                    navController.navigateToMovies {
+                                        // Pop up to the start destination of the graph to
+                                        // avoid building up a large stack of destinations
+                                        // on the back stack as users select items
+                                        popUpTo(navController.graph.findStartDestination().id) {
+                                            saveState = true
+                                        }
+                                        // Avoid multiple copies of the same destination when
+                                        // reselecting the same item
+                                        launchSingleTop = true
+                                        // Restore state when reselecting a previously selected item
+                                        restoreState = true
+                                    }
+                                }
+                            )
+                            NavigationBarItem(
+                                icon = { Icon(Icons.Filled.Menu, contentDescription = null) },
+                                label = { Text(stringResource(R.string.label_genres)) },
+                                selected = isRouteSelected(currentDestination, GENRES_ROUTE),
+                                onClick = {
+                                }
+                            )
+                            NavigationBarItem(
+                                icon = { Icon(Icons.Filled.Search, contentDescription = null) },
+                                label = { Text(stringResource(R.string.label_search)) },
+                                selected = isRouteSelected(currentDestination, SEARCH_ROUTE),
+                                onClick = {
+                                }
+                            )
+                            NavigationBarItem(
+                                icon = { Icon(Icons.Filled.Person, contentDescription = null) },
+                                label = { Text(stringResource(R.string.label_profile)) },
+                                selected = isRouteSelected(currentDestination, PROFILE_ROUTE),
+                                onClick = {
+                                }
+                            )
+                        }
+                    }
+                ) { innerPadding ->
+                    NavHost(
+                        navController,
+                        startDestination = MOVIES_ROUTE,
+                        Modifier.consumeWindowInsets(innerPadding),
+                    ) {
+                        moviesScreen()
+                        genresScreen()
+                        searchScreen()
+                        profileScreen()
+                    }
                 }
             }
         }
-    }
-}
 
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
-    Text(
-        text = "Hello $name!",
-        modifier = modifier
-    )
-}
+        lifecycleScope.launch(Dispatchers.IO) {
+            val connector = MoviesConnector.instance
 
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
-    FirebaseDataConnectTheme {
-        Greeting("Android")
+            val response = connector.listMovies.execute()
+//            val response = connector.createMovie.execute(
+//                title = "Empire Strikes Back",
+//                releaseYear = 1980,
+//                genre = "Sci-Fi",
+//                imageUrl = ""
+//            ) {
+//                description = "Hello World"
+//                tags = emptyList()
+//                rating = 0.0
+//            }
+            Log.e("Main", "$response")
+        }
     }
-}
\ No newline at end of file
+}
+
+private fun isRouteSelected(currentDestination: NavDestination?, route: String) =
+    currentDestination?.hierarchy?.any { it.route == route } == true
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
deleted file mode 100644
index 762b323d7..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MoviesViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.google.firebase.example.dataconnect
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.google.firebase.dataconnect.movies.MoviesConnector
-import com.google.firebase.dataconnect.movies.execute
-import com.google.firebase.dataconnect.movies.flow
-import com.google.firebase.dataconnect.movies.instance
-import kotlinx.coroutines.launch
-
-class MoviesViewModel : ViewModel() {
-
-    fun fetchMovies() {
-        viewModelScope.launch {
-            val connector = MoviesConnector.instance
-
-            connector.listMovies.execute()
-            connector.createMovie.execute(
-                title = "Empire Strikes Back",
-                releaseYear = 1980,
-                genre = "Sci-Fi",
-                rating = 5
-            )
-
-            connector.listMoviesByGenre.execute(genre = "Sci-Fi")
-
-//            connector.addMovie.execute(title = "", genre = "")
-//
-//            val result = connector.listMovies.execute()
-//            result.data.movies.firstOrNull()
-
-//            connector.listMoviesGenre.flow(genre = "")
-//                .collect { data ->
-//                    val movies = data.movies
-//                }
-
-            // Connect to the emulator on "10.0.2.2:9510"
-            connector.dataConnect.useEmulator()
-
-// (alternatively) if you're running your emulator on non-default port:
-            connector.dataConnect.useEmulator(
-                port = 9999
-            )
-
-        }
-    }
-}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
new file mode 100644
index 000000000..686c9efbb
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
@@ -0,0 +1,21 @@
+package com.google.firebase.example.dataconnect.feature.genres
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val GENRES_ROUTE = "genres_route"
+
+fun NavController.navigateToGenres(navOptions: NavOptionsBuilder.() -> Unit) =
+    navigate(GENRES_ROUTE, navOptions)
+
+fun NavGraphBuilder.genresScreen(
+
+) {
+    composable(route = GENRES_ROUTE) {
+        // TODO: Call composable
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
new file mode 100644
index 000000000..9cc3717c3
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
@@ -0,0 +1,21 @@
+package com.google.firebase.example.dataconnect.feature.movies
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val MOVIES_ROUTE = "movies_route"
+
+fun NavController.navigateToMovies(navOptions: NavOptionsBuilder.() -> Unit) =
+    navigate(MOVIES_ROUTE, navOptions)
+
+fun NavGraphBuilder.moviesScreen(
+
+) {
+    composable(route = MOVIES_ROUTE) {
+        // TODO: Add Movies composable
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
new file mode 100644
index 000000000..156ba105b
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
@@ -0,0 +1,21 @@
+package com.google.firebase.example.dataconnect.feature.profile
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val PROFILE_ROUTE = "profile_route"
+
+fun NavController.navigateToProfile(navOptions: NavOptionsBuilder.() -> Unit) =
+    navigate(PROFILE_ROUTE, navOptions)
+
+fun NavGraphBuilder.profileScreen(
+
+) {
+    composable(route = PROFILE_ROUTE) {
+        // TODO: Call composable
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/search/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/search/Navigation.kt
new file mode 100644
index 000000000..fee3bdf59
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/search/Navigation.kt
@@ -0,0 +1,21 @@
+package com.google.firebase.example.dataconnect.feature.search
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val SEARCH_ROUTE = "search_route"
+
+fun NavController.navigateToSearch(navOptions: NavOptionsBuilder.() -> Unit) =
+    navigate(SEARCH_ROUTE, navOptions)
+
+fun NavGraphBuilder.searchScreen(
+
+) {
+    composable(route = SEARCH_ROUTE) {
+        // TODO: Call composable
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index 8c9ac0b83..a2cda77fb 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -1,3 +1,9 @@
 <resources>
     <string name="app_name">Firebase Data Connect</string>
+
+    <!-- NavBar labels -->
+    <string name="label_movies">Movies</string>
+    <string name="label_genres">Genres</string>
+    <string name="label_search">Search</string>
+    <string name="label_profile">Profile</string>
 </resources>
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bed607ba5..696b8a681 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,6 +11,7 @@ lifecycle = "2.8.1"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
 googleServices = "4.4.1"
+composeNavigation = "2.7.7"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -30,6 +31,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
 androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
 androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
 androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"}
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }

From 12731493031636ae5e5c56772dda9abc864366d3 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 3 Jun 2024 04:57:36 +0100
Subject: [PATCH 08/63] feat: create movies screen

---
 dataconnect/app/build.gradle.kts              |  2 +
 dataconnect/app/src/main/AndroidManifest.xml  |  3 +
 .../example/dataconnect/MainActivity.kt       | 24 --------
 .../dataconnect/data/DataConnectMappers.kt    | 15 +++++
 .../dataconnect/data/DataRepository.kt        | 14 +++++
 .../example/dataconnect/data/Movie.kt         | 14 +++++
 .../feature/movies/MoviesScreen.kt            | 59 +++++++++++++++++++
 .../feature/movies/MoviesViewModel.kt         | 29 +++++++++
 .../dataconnect/feature/movies/Navigation.kt  |  2 +-
 gradle/libs.versions.toml                     |  3 +
 10 files changed, 140 insertions(+), 25 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index db4b72919..ff39640d0 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -66,6 +66,8 @@ dependencies {
     implementation(libs.androidx.ui.tooling.preview)
     implementation(libs.androidx.material3)
     implementation(libs.compose.navigation)
+    implementation(libs.androidx.lifecycle.runtime.compose.android)
+    implementation(libs.coil.compose)
 
     // Firebase dependencies
     implementation(libs.firebase.auth)
diff --git a/dataconnect/app/src/main/AndroidManifest.xml b/dataconnect/app/src/main/AndroidManifest.xml
index b5ed84e4f..230081caf 100644
--- a/dataconnect/app/src/main/AndroidManifest.xml
+++ b/dataconnect/app/src/main/AndroidManifest.xml
@@ -2,6 +2,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
 
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
     <application
         android:allowBackup="true"
         android:dataExtractionRules="@xml/data_extraction_rules"
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index 4996cbe10..cccc1901f 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -1,7 +1,6 @@
 package com.google.firebase.example.dataconnect
 
 import android.os.Bundle
-import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
@@ -20,16 +19,12 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.lifecycleScope
 import androidx.navigation.NavDestination
 import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
-import com.google.firebase.dataconnect.movies.MoviesConnector
-import com.google.firebase.dataconnect.movies.execute
-import com.google.firebase.dataconnect.movies.instance
 import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
 import com.google.firebase.example.dataconnect.feature.genres.genresScreen
 import com.google.firebase.example.dataconnect.feature.movies.MOVIES_ROUTE
@@ -40,8 +35,6 @@ import com.google.firebase.example.dataconnect.feature.profile.profileScreen
 import com.google.firebase.example.dataconnect.feature.search.SEARCH_ROUTE
 import com.google.firebase.example.dataconnect.feature.search.searchScreen
 import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
 
 class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -113,23 +106,6 @@ class MainActivity : ComponentActivity() {
                 }
             }
         }
-
-        lifecycleScope.launch(Dispatchers.IO) {
-            val connector = MoviesConnector.instance
-
-            val response = connector.listMovies.execute()
-//            val response = connector.createMovie.execute(
-//                title = "Empire Strikes Back",
-//                releaseYear = 1980,
-//                genre = "Sci-Fi",
-//                imageUrl = ""
-//            ) {
-//                description = "Hello World"
-//                tags = emptyList()
-//                rating = 0.0
-//            }
-            Log.e("Main", "$response")
-        }
     }
 }
 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
new file mode 100644
index 000000000..6d80d4da9
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
@@ -0,0 +1,15 @@
+package com.google.firebase.example.dataconnect.data
+
+import com.google.firebase.dataconnect.movies.ListMoviesQuery
+
+fun ListMoviesQuery.Data.MoviesItem.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        genre = this.genre,
+        imageUrl = this.imageUrl,
+        releaseYear = this.releaseYear,
+        rating = this.rating,
+        tags = this.tags
+    )
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt
new file mode 100644
index 000000000..1b23f1ec2
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt
@@ -0,0 +1,14 @@
+package com.google.firebase.example.dataconnect.data
+
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+
+class DataRepository(
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+) {
+
+    suspend fun listMovies(): List<Movie> {
+        return moviesConnector.listMovies.execute().data.movies.map { it.toMovie() }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
new file mode 100644
index 000000000..c4466547c
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
@@ -0,0 +1,14 @@
+package com.google.firebase.example.dataconnect.data
+
+import java.util.UUID
+
+data class Movie(
+    val id: UUID,
+    val title: String,
+    val imageUrl: String,
+    val releaseYear: Int? = 1970,
+    val genre: String? = "",
+    val rating: Double? = 0.0,
+    val description: String? = "",
+    val tags: List<String?>? = emptyList()
+)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
new file mode 100644
index 000000000..ef63d19ed
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -0,0 +1,59 @@
+package com.google.firebase.example.dataconnect.feature.movies
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.AsyncImage
+import com.google.firebase.example.dataconnect.data.Movie
+
+@Composable
+fun MoviesScreen(
+    moviesViewModel: MoviesViewModel = viewModel()
+) {
+    val movies by moviesViewModel.movies.collectAsState()
+    MoviesScreen(movies)
+}
+
+@Composable
+fun MoviesScreen(
+    movies: List<Movie>
+) {
+    LazyVerticalGrid(
+        columns = GridCells.Fixed(3),
+        modifier = Modifier.padding(top = 48.dp)
+    ) {
+        items(movies) { movie ->
+            Card(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(8.dp),
+            ) {
+                AsyncImage(
+                    model = movie.imageUrl,
+                    contentDescription = null,
+                    modifier = Modifier.fillMaxWidth()
+                )
+                Text(
+                    text = movie.title,
+                    style = MaterialTheme.typography.titleMedium,
+                    modifier = Modifier.padding(8.dp)
+                )
+                Text(
+                    text = "Rating: ${movie.rating}",
+                    modifier = Modifier.padding(bottom = 8.dp, start = 8.dp)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
new file mode 100644
index 000000000..175286962
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
@@ -0,0 +1,29 @@
+package com.google.firebase.example.dataconnect.feature.movies
+
+import androidx.compose.runtime.Composable
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.firebase.example.dataconnect.data.DataRepository
+import com.google.firebase.example.dataconnect.data.Movie
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class MoviesViewModel(
+    private val dataRepository: DataRepository = DataRepository()
+) : ViewModel() {
+
+    private val _movies = MutableStateFlow<List<Movie>>(emptyList())
+    val movies: StateFlow<List<Movie>>
+        get() = _movies
+
+    init {
+        viewModelScope.launch {
+            try {
+                _movies.value = dataRepository.listMovies()
+            } catch (e: Exception) {
+                e.printStackTrace()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
index 9cc3717c3..5861da685 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
@@ -14,7 +14,7 @@ fun NavGraphBuilder.moviesScreen(
 
 ) {
     composable(route = MOVIES_ROUTE) {
-        // TODO: Add Movies composable
+        MoviesScreen()
     }
 }
 
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 696b8a681..5e230d1ca 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,6 @@
 [versions]
 agp = "8.4.0"
+coilCompose = "2.6.0"
 firebaseAuth = "23.0.0"
 firebaseDataConnect = "16.0.0-alpha03"
 kotlin = "1.9.23"
@@ -15,7 +16,9 @@ composeNavigation = "2.7.7"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycle" }
 androidx-lifecycle-viewmodel-android = { module = "androidx.lifecycle:lifecycle-viewmodel-android", version.ref = "lifecycle" }
+coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
 firebase-auth = { module = "com.google.firebase:firebase-auth", version.ref = "firebaseAuth" }
 firebase-dataconnect = { module = "com.google.firebase:firebase-dataconnect", version.ref = "firebaseDataConnect" }
 junit = { group = "junit", name = "junit", version.ref = "junit" }

From 30d629817bd42cd076833339b38f5fa6a0e81937 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 3 Jun 2024 05:16:59 +0100
Subject: [PATCH 09/63] add firebase data connect logo

---
 .../drawable-v24/ic_launcher_foreground.xml   |  30 ------------------
 .../res/drawable/firebase_data_connect.xml    |  18 +++++++++++
 .../res/mipmap-anydpi-v26/ic_launcher.xml     |   4 +--
 .../mipmap-anydpi-v26/ic_launcher_round.xml   |   4 +--
 .../src/main/res/mipmap-hdpi/ic_launcher.png  | Bin 0 -> 5439 bytes
 .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes
 .../res/mipmap-hdpi/ic_launcher_round.webp    | Bin 2898 -> 0 bytes
 .../src/main/res/mipmap-mdpi/ic_launcher.png  | Bin 0 -> 2765 bytes
 .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes
 .../res/mipmap-mdpi/ic_launcher_round.webp    | Bin 1772 -> 0 bytes
 .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7233 bytes
 .../main/res/mipmap-xhdpi/ic_launcher.webp    | Bin 1900 -> 0 bytes
 .../res/mipmap-xhdpi/ic_launcher_round.webp   | Bin 3918 -> 0 bytes
 .../main/res/mipmap-xxhdpi/ic_launcher.png    | Bin 0 -> 12140 bytes
 .../main/res/mipmap-xxhdpi/ic_launcher.webp   | Bin 2884 -> 0 bytes
 .../res/mipmap-xxhdpi/ic_launcher_round.webp  | Bin 5914 -> 0 bytes
 .../main/res/mipmap-xxxhdpi/ic_launcher.png   | Bin 0 -> 18036 bytes
 .../main/res/mipmap-xxxhdpi/ic_launcher.webp  | Bin 3844 -> 0 bytes
 .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes
 19 files changed, 22 insertions(+), 34 deletions(-)
 delete mode 100644 dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
 create mode 100644 dataconnect/app/src/main/res/drawable/firebase_data_connect.xml
 create mode 100644 dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.png
 delete mode 100644 dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp
 delete mode 100644 dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.png
 delete mode 100644 dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp
 delete mode 100644 dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.png
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
 create mode 100644 dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
 delete mode 100644 dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp

diff --git a/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d114..000000000
--- a/dataconnect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/drawable/firebase_data_connect.xml b/dataconnect/app/src/main/res/drawable/firebase_data_connect.xml
new file mode 100644
index 000000000..e2106454e
--- /dev/null
+++ b/dataconnect/app/src/main/res/drawable/firebase_data_connect.xml
@@ -0,0 +1,18 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="192dp"
+    android:height="192dp"
+    android:viewportWidth="192"
+    android:viewportHeight="192">
+  <group>
+    <clip-path
+        android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0"/>
+  </group>
+  <path
+      android:pathData="M97.07,126.87l-22.22,-22.22c3.8,-6.69 2.85,-15.35 -2.86,-21.06c-6.85,-6.85 -17.95,-6.85 -24.8,0c-6.85,6.85 -6.85,17.95 0,24.8c5.71,5.71 14.36,6.66 21.06,2.86l22.22,22.22c6.65,6.66 15.37,6.71 21.03,2.06c5.69,-4.67 7.18,-13.16 1.67,-20.75l-27,-42.48l-0.1,-0.13c-2.81,-3.81 -1.65,-6.82 0.17,-8.32c1.87,-1.54 5.26,-2.16 8.68,1.27l15.97,15.97c1.82,1.82 4.78,1.82 6.6,0c1.82,-1.82 1.82,-4.78 0,-6.6l-15.97,-15.97c-6.73,-6.73 -15.55,-6.55 -21.22,-1.87c-5.69,4.69 -7.41,13.25 -1.84,20.92l27,42.48l0.1,0.13c2.88,3.9 1.65,6.79 0.02,8.13c-1.69,1.39 -5.01,2.06 -8.51,-1.44zM53.8,101.8c3.2,3.2 8.4,3.2 11.6,0c3.2,-3.2 3.2,-8.4 0,-11.6c-3.2,-3.2 -8.4,-3.2 -11.6,0c-3.2,3.2 -3.2,8.4 0,11.6zM120.27,83.87c-6.7,6.7 -6.7,17.57 0,24.27c6.7,6.7 17.57,6.7 24.27,0c6.7,-6.7 6.7,-17.57 0,-24.27c-6.7,-6.7 -17.57,-6.7 -24.27,0z"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"
+      android:strokeLineCap="butt"/>
+</vector>
diff --git a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755bf..8eeb203f9 100644
--- a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+    <foreground android:drawable="@drawable/firebase_data_connect" />
+    <monochrome android:drawable="@drawable/firebase_data_connect" />
 </adaptive-icon>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755bf..8eeb203f9 100644
--- a/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/dataconnect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+    <foreground android:drawable="@drawable/firebase_data_connect" />
+    <monochrome android:drawable="@drawable/firebase_data_connect" />
 </adaptive-icon>
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..40f804ae7dfe13bf5e8439bbbaf16d7db792f971
GIT binary patch
literal 5439
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1404
zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r
zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN
zXl<@<#Cm<R)d19?=)E<{+g@mp0C)CAX%7ksNnpX=jPlJEkqD9%o*fC(U7iySOYHHS
zCLH@bXPyI|^Z)Mc^PG7Oc+NfBJO`d7p5;U`M53Wbd!w|M(MUoNRc2m{@^!wFKzL?&
zx_RAc-^9Azxo%DmXW^9GV0~;n_G9&dym)|QrMEx!y_F=oDunn;1y)cvAc6z{0MHiz
zodGIH07w8nF%*bGq9Gv`YHnm80|d1T$uW=u4vGV%tX#>e5f5yr2h%@8TWh?)bSK`O
z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF!
z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!<r*4*+?g*sysFgiN}}d!-T(>jH(_Z*nzTK4
zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG
z?Fe{#<RA5Hg_mG&BZS>9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~
zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd
zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK
zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG<W<SKpuS2eaDGkL-J-
z!+&UV_N0e<DV*|igwqdC*l{lj0T&&`uq=ycU@f9x0i8jzLa(y*X(YV?PCtUw;ks}p
z4zFn7N(-OG^(9ot1ZhYISOWe9?+l%f6v41n+j<OM$_uJgNP?ZJy}hEMMH=;WiG4I`
zs(bIWwSD<LJnAN(E(Xjr4k(^UYF37F_3f{;E%%FEa&I3I0GbdH@{pD5$m+1PN5CW)
zyZ&*9o#8wstdx@^rci;0B4BP2+H4Y<KbJI5L(bXG(k@`Kp=d>8I5h*uf{rn48Z!_@
z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqro<oln8GL@_6LPC)kg1!M)Y!|NCn7b*0sG
zEN=&c2xMM<>a($ne7EUK;#3V<N-jQ7j($tREa0F&-HzYCQtR#fTCZMRN*ZSm;T7a+
zuxa$}zDL8R9wGYkHb$+gJoiM@z+u{u7a_VUBwtd)bPzHxH}C`W=^2PsBr`s7taBMG
zm#Ss=-o`)W8%%x%>YkXaew%Kh^3OrMht<?zOY6P}#rBhn_hrWY$_P`{#CBR9w?+E6
zt7r#NN-tjxFY{9q75P<|L<ZJBHwn4FhG(&i>jYN?XEoY`tRPQsAkH-DSL^QqyN0>^
zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y
z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu
z4)<UE%?IC0Du41FrE~F_qc8nOq>P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6
z>%NbT+5*@L5(I9<vs%8>j@06@(!<!eaZcF<_le1MVaYMg=gRy*f2#IaBH-mJIpy+L
z=Gsbhd6=3>{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W
Kmw_-CKmY()leWbG

diff --git a/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2898
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

diff --git a/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..64276c653d2509aad307e5cf1587bd5a930b7f9e
GIT binary patch
literal 2765
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
T({?pW00000NkvXXu0mjfu=P;|

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 982
zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^
z^1csucXz7-Qrhzl9HuH<!ckn_w-(t15itRHmqN0O$B3XH(E|jyV^QXq8=yM`Q**vy
zpEpgQd+no=J<Tlv&+_>B%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N
zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}<p
z;bEy|mw1;}P&gp|0ssKe4*;D3Dlh;r06r}ehC-pC4mGy`3;|+W04+#B4r_x~@mHHy
z)H}bD|I2-n_L$pW;*I)~?=#N<)`92&<$3IR`#<SH6@I&FRQa6xBmQ5wPwJ2PAI(ne
z$L2Yb@JHxb`+bLk*AjR$^`b?pr|?!6=+AboIQ2D-p)UI7x(J0|5(5~ur$_+)`>C%4
zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{
ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT
zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh
zOBLA>$~P3r4b*NcqR;y6pwyhZ<hjKiZs6mOSFB&+cIl`GV$93-<ciUjF#*1^<p~gh
ziQ_{)r0dA7$It&Fe=obxu8n!+elxmgqxPbUL!FxW0;AOfqz@8JOz9Qbm)m-9!^7D)
z480@BoIIb<oT``+rVla8L)8fXO&6}3P9n4v$`6WG<DUNWuKb9J9rUsAn7d-_YWT^U
z{NXl@OAPIJ!>3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu
z(U^&XLW^Q<WhKYr9rXr6*~Tmpuq6NjnD6;;NNBGIg-1ZvfACQ4{ocrwM0)?`oL2ts
zCXY5KT@`(ir63J0?%+_(-dDgf<6R$u{lCdy6Zi5d+Bf;1OXyD;xe3#Gug*&T|0o41
zD8;$|JvUv&@vsLIH&C5+S{!k&{~Z54^y@9r>X7F9Z*SRPpQl{B%x)_AMp^}_v~?j7
zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0
z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@
E0Of=7!T<mO

diff --git a/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611da081676d42f6c3f78a2c91e7bcedddedb..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1772
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
O>FJ$wK}0tWNB{uH;AM~i

diff --git a/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa4fd7ba1708e64d30918dede5a9fb2679a22348
GIT binary patch
literal 7233
zcmZWuc{~&T|KEn($`Ku|QYbeeITmtMn45AF${ljfM&v#!B#}81P0STJN0d-T%v{-!
z+?iuGn{8j8e}4b`{&*k%J>ReA<N16)-tXrt&C<ezm+KrC007{<ZF<Z4cy<48aGW@v
z7hgwS2LMDlZr{?kjdI;8=6r6C%^%qIzppBtt8!z|%+v4X#3j2Qt3M{pIE~qi3{oXM
z_<{M@6ZcN@zq32Xdivfa`KR*brrcIu#qL5`H`3D5(?%TAEG8~Vk8zHMvvdXHyNk7$
z9i5SQx@%<po8(Do)%pbcZ&blH8BB&_y0*K8%g=n3HC9Qt%JdYf_`mJ`^HS|w+xs`h
zl-ln9$VADvX+{)~&1kCI@Y$=KZ(2f?CL4Q()iHb3kFpDbiWk%;3!=Ai<~y#nMV5_K
z5g#36!ZXj@)3h$$g5f>G1qK7lAwC(hkl<Zcvmm19;C57=<CfJ?P>yG0)I|40Max(5
zPl~(MhtebGb^MB#t@^@q)={MDpqD0}9Ky2qR;Nyji;1m`z2KjxsT<ynuyvC*#%7*E
zgv0;3K}Y#^ruQuxB%AtT<+`vH6LH1lm^^0;5vP2VUmU4=G<z1t)`$LD@#+~5)A$~}
z9}D62mhX9R0?xY<l?=bU@=(~Fl{=)(M7<!|mTsQI2k-OO)v13FiT&y+$+^&m9(J=G
zH7tij_zoscvv*zZtJ>EXQQ(0K_ThWlUa>iTC2lXSyNwX7=&dSHTP8Fa-epI(X`{-h
z9@U&Va;%}Igz<S%@Sc=Y?$1r^3PN2M$I5{7vnh%KIL2S+v03_Wy-EH2MY%SY%N<Q8
zqt7No;t|s$#5U@9(Y^RA9{AfjnBz$Yc##u*i1uNpy(ebfjFjU+si-8GQW~i#%16ET
z;}eQSngHJ!;$oenGy=iMQ#DJlYYtEHI=!ECU6&j(__i&jD=MhKWcqFKN#!G&mKA*_
z#yUXv7tM+;7fw?0ptN4y4o)D4ku)EZG$ZWkcpr)}z#ANX1`D?AlPG2TY?>Yqtyl-`
zJRy@Aj;y-!)Rl<XZ51f!XwHxaQ3K9c*PKjTPa%bnWZI~L-)IGEX$3Xt{2KJLE_AeF
zAL8s5y&ASg4*;thYN4@d08<Y4Tc3M|#Xpxmh{HZx3@7VCFD1GPR5m-wW5Ao<q50?Z
zuDbPM8c7c_Nw3#6=X2IgR#<3l<_=7d#S|vM8_fHDuVKqKPyLY3j&Tx*3<N5(J5~TL
zQVgo0EAH^ebu^fswZ8V(dxv+npLN=i1Q;wHl(Q}3LE7G=DtBFb(!gix>@;k~9jvK#
zbcm#KZ?e}`d4cNy2OQ6H>kHX`n9Bap5ccS#b?XV}cl3j;E6Liz@w+@=3~`SK%uXRY
z5nHR*A$$Ero*TD+S0KT?Sc#SOoX4crCOYv;#(gV#J1M|mLn|wci(6U$5G@2a@M@P(
zxS7Cq@1ncvE5XL(AchjSN;Q%q(C|kJ+ZbA~hiV?Kk>-Y$D9B)lVdr4>eHehA6C|b!
z30tCb2I(&89DcJrCNMB&H?Zx(D$E_so16HrYI2_;-!sMmM2@jN;@^C6DrQ;dqYXm>
zx9d+TeQDmQs`MD|pTo@wuX2zp^CwnQ=lIybRSUjnVXtas`X+OeN$!iw>yUD48_BGm
z-aifVJUs-PX{I+j{)Vv3LUd{Hx2}iDto8EWg>R0qeAiDn1zYsG@%ZiSX4I}fdqKd~
z*u_zembYTghdk#CH3tcMJbDAaEk>RqyeVgs5y>QCf06Ug4>kYM<2D59fqkYu8b6St
zBRUYqQzuzppWz@x-h2h{5wq;u0DNQ%xNvbir52^`rg<w4Aytt7@i9I!hr{f1^KY7%
zOyQ5<rkA}dXQ0`=D;n%XH6HL!qK&^TNycGy9d|I!M^j;7enXlL;8%L`_*j))p1ZP1
zj8)ux&o&{_|Bn9HnR_7_i7Mb!xv(Chf{SBTU5j#j^-Fl0&7x5)*xImQkPx<cc(2D7
zkE!3TnvSt{reCRJTtXsb@RY4%N<6@u%-R`dk*_~+NEG;kaWYbWAQNyP8_xmotK7`a
zR6cPXIS0x{4M3mrKhyb!Cr_CwxoBGRglZKxmS%ZssW=uECH{~CIvW<WS@mM-Z2l{t
zcA@GsHP@M*=AA%q?t@J+ofeqJ@f#rEIc0X@CFhjej;&=d8CKwr2LZQMWp|<hxY3;_
z!Xa0XW{mkyM{0fpQ6_!^X_yXqi<r^-t_yXi3q|{Ws7>fNJb@Wr50l2#upWJ61OJ@N
z{hOxw6^lqZ_V>UvY%u`5b*v-qPXJsB+rwfcYjrEDyYd}CCg=y-xAe?Y@#pjH^OyVs
zR?l6>?r_vBA*?hl=$*+mof2<<9pnk8j*mxXkX7Z_ygxr};!z#G+2ns_U-xQLP@Ns>
zY_~KEMUI7XrKRLu9pm~eq<BlF#nikfP}2|SW5m(35#L@TbY_~f(8@iH;PldhWd{EB
z$!AvwE&GZ1>}}upCEp3RTGjzfKPKq4?E5xni~H|hWoX+Bv+l@MfnNJ?-&Xs<6Z&kV
z<Me}6Rzg&Mn}JIPN`;tvZ0BR;9+$Pr4AcR(HUbpx$4g7~JW%Mto)a+c&F?i;wvz`|
zAW1bxt=@8a;ZTL{6mGE|-f%`?RscM!lkZXcWKFP~AR=r!JoZwMpj(=f@XoDgl7&3H
zh0aLHi#9yAToqqma85-mdJVHsc4_jHpgcL?Ofx_sEFbVw?F;Cd*~IgGS>=nSrDu4h
zv*1goA0g*#+gl2#JL|D!zjnF;SIuoMhx;umek{aYRKQ)h+tLPY*Y(Wk53*2A^Lys~
z+HCIq&7qH@?h22O`UhUEW0r2PNMJGl$=ZV%tPc7y2{N-eJ;ecR=AFLy{i?Bg(KRgZ
zXvF;z#j-u}!}^{jn&HEn!IPs!XJgb*M)#kGQ6h_$DxFqR^%N_DGA0kpA+yjIj}S_x
zf6Yx#e5?25eiFL;P2@_difyB?DVJ9U3&9l1w+Pg!8^9?{9E3@+%2racRq|l9FICa-
z!e40EtHsy?>pgQfF4lO>zH~{@CJX21AW+NgVO-@`Kxd$ecl(Uhn%cGf>2o?xp0m7E
zvXkYmOMy+R(Vpmz`-Zc=Dhik8#;OJJra|+oU2PlWGH-Lv0!-#Vjz7hB{|Oj^5xLVd
z&0a1-Y?Yf`1xopQ-wCli-<m+==KnK@ls{4N^JXyHej70SaNca;_hzNXuwsp%c&*x3
z-y+}Cs68`%*oeTdBKpuMy4tKxAeDuC2mXkjL2HvV4cFPlpE`+CqF!6}gDrU~rT$^u
zNp?iKgOJH`Y`tjSfLy3cg}2JDk)h_Fr87XW?ZM=rc@~(jjoGr_#GQ?<+6T468%ZXH
zg^r`P(x!i}<^POQpHD63x3%z3QV#T4y&sGe3;tmdu6uMi5f<6@F|F|mSxWUKs-DB<
z&Xd?PFDRcAvi({Eza1RhqeiNuWnd<{FL|Rf?|T*fvc~E-+}-P!arTk9c1}92^;{JP
z*Rt(9l23E+8;{JEG2yS`3a=dvrP$Ko!}4X&rQY}uJ(Hc^IF)zf)!t)ZOKL%EB4(|}
z^kMORq_nSG9O0YK{`MmX($OpF>U^f7SJ{lJq4|lPiZp^E-}%%J+Wij;ra3d(Z<j;p
zti6VgE4Spf7*nu{Y+1(n<TKfl)i<uBk4!%EO(yuv$GLK*p;GF{?_MEg=$84P`rvRc
z$^WjM)$0DNiH+#wLz0ZW+Kr#0$?2M5)@?Rh?<)*xnHuLd&TM+s%0~ZKj`&jk@2b_v
z?e8(U4jU7$8%mCw8hm!uY-1S(U--*Acx=+XPZo`ECH(B9JG+NbZa{NJZoRfYXWK5-
z-EfY@bta2#-nMf;(q^kY-vZE?tcX+P5vMtZg8lPxx3v2oDRu}7JaI@D@ka?IeKgGe
zWZfDmt-Yz?d@|zdxY^So;XAUp5ZTf<<~AE!E3(f`<Ko7j$ba$Yzj)O7@{Ld432tfI
zj)ZLfm8T{qeS{^SP@}K@MLQy|6)7F3quLOkjJGy@Cewh59g6J7uc#cC3!cS)e74TK
z`?RWZH@ar!Rc+~-sl3YOfzq`b``=Bw>lInm_fqKc7AOOLpIaHv8Ag{4w@<fdsja*|
z(euyGl62zerE(PfGOBktPM%l%d`97?cubqV{FB2cT7Jgq-6ken2IAu=6O%P&j=}xA
zFnMzAWj)pg&E`#W1t*TSu7DeYx>w(<FvXT<*9|R~Vtv>_Q-c@ZN!>~f6ct5Ap%q$?
z$AsCDPIS&$j1iW*^VwX3WX}=tI}`LQ-s1CJzAiAA?25jx%@uebLb1ty4Rf2{G$%Wg
zC*m|cOEJ}hwc!<mBnYXRq)A&XR;EPR(Lj~3L&J1%w2V4FrcNn&&EE5*1t;+5K+%Wg
zze*<vS>6|X8#quYMyY|~<=RC8=_uVCYe>Egb=f?Rv3Bl`o@&D4U9eP_-nCwk4${@3
z6J7Xu28cP0a6xT94%q+mR&T_wz^$Ny8rpF1UHL@N%5uaB+CQ<an&F?y!sXG+AwyV)
zXOf36Fky0s%IYJO^WKp9_l*K~(0guW!@tlo=aW&h&RT6F9@;S;yh?=mWn8}VPFFR(
zPA`wXBv-b*U{YSFicwfZZZAh_g!+mQ93E2azYZ&qwr|DbKL)o2?WDG(sktP(q^}S6
zll{9dSO0BH6-9=<7MZgOWgGKsp3}~kEqKB&bn9=TX0^w+vkcKOXNLNa%&Z}<za^WK
z+r729{`_yuz~n(C^rOoDC#x5+{wE>zUC@qRjds>x<b1pd_B@e%l_GFt4-wNLyrUVp
z6?hbsQ;R&|q*MA<_Y#w?TbB|v5OT)!Id~cL#zJ@SzaZ7d-QTAF@Cw_9clfU_<=_RV
z(C&ux`+wt%BhBnn64jFqs0X_k%kB?}W5WE?h`eDWc|k2IT52#JXQvd50(E|fVPV1`
z;=N^Ci(LP~#ne+2WG<Z4MyaAdgrz=dI`8>VU((o<Nj41D2qU`qIjg4n4InI@jE}sZ
zn#VdPw-G?Xm5;`>?yxqayt}T%5*&J0w5*i^xgEfS3Mxt1V7+a*Y=aqa$!Q9QA=Vs=
zZfQ@T+gtNG$)j%ocrC{ANBG%@D|{yf?23g7@s{_pM4h94u*mRbXHtchZS+Yi_t&c<
zc14bg=0g|%4&FFOv=iq27X=GRxj*-a|9%14M(P{u>^<5W;KEO}{ex<luw>+B7hz^8
zb8cMlPoUirY1g#{7>q3GEkX_y%0wS<5+(NkBwULz&FkRl5K3WBpF!R|yMI&PPFl$>
z`pg=4Ii!SlQf8?6%%>)%)V{PACa7TYQVukeqrIxVvB?lu$ygaI^~Pp=?%&%m#Qj0y
zy;tF<Kl&}u9i0`Zd2?WD{R`%HEOdBhVK%~xpcZA@#mbCNW<Hm}d}#!<{)cH-kb1)c
z**<mW2>(tgGi(BqVHN4c94!in4S#kcv0{Z&s=q;{%WzMBy;fwxlkcMYC9^N(8>lM|
zH27qs*UD6@&Z(QTkdKSOhB(pSKln~aFU~cmLVY)tw@1nx>qNM1uj_l6i55V4whh<c
z?d{1CJttY+w{O&7A?#!z+D08N(=3efMZ?bw>1B+64mVwG?}tPVeM;<%eHNIN<|ou#
zdwvM<Njj)xZuZ~x0R(iJE7Ic%Jn<Fb%~IwO!wE|Hd-E(`!9qAfXSr%2_d`sN36|xk
zued`CEqB#9*@Y|lsJD#4b1wRY#U4D@n&Zh|z3~1b6C-S?z>p}zYc;DSpio3$Vd`De
zm9W*;1%C|-OSwl)=yxW-`{NV-IZ1Ut84IVU_lE82=3T#c)52HZETtV)tXRz>p;Or>
z-JJLcMaY6zUVV5ew%6OLo7SS8rbk2Z;D?n;$x5lD_(XmnC`2Q$dn^v6BjB1JAw(PF
z8~UV?q?gi-K6nFR_YJGhN}yl*{YC>LH}~FDN5ke2EiBm<cdFZF^^L_6QBFWl$8E-f
z_2~JQGSpj313F^Tf9cp5i#^X44--XH_p^U-c;2c{Qa-6G$}kSTLytkzUTJ6b-;Ir8
z&7;z8Gh};a;vEJX-r5-AR+h{sy7xET6KjiS2XQS)b!*Bm&GYbd2r#Pob@tY0VjPBC
z%TBBWB>s1=B#NZ6U;i+^&t`?WH@H$KdI4zy%{Gd{#%;V!7V>a@BpJV!TJLb}ZS9sO
zn9l_fy}`-!NuR^GIp3MaT`-W5DyvQ3#cPAFEwtxD0NZAtJ!Z#<63b9$DAs$b(!HmZ
zT8f_6r+QLy;;vrU<WG2^T&+1fW}f$fYM2U~<lHHcsG{7>d8U_b+S!@1l3=YaauYB$
zD)i|duASnq!#QAxlZni>o*kZ?42{86=F#YCp$(T=6#Q6Fg0ARXeZ&WXl*{ZwUdgsC
zEQKf`$XR?Q@;_sjD0RC4iuZ2z4_q{=H%?@VPWA}gP-P6;#?sls1OB3Iei9)cZbv&_
z`3iSF3raWb{35l0w8p-?4|12g*hZcl{WJwjRnj%&v@DCb-q3M{9A&?gIu#|13TS(~
z9AP7V@!PwNC_SJKYwA-s!r?ha1qjr+w!0B3dL04<*3)(Hw;eKN`9Homm0Id#=c@GG
zuGv)!=Pa&Z@Y1LnVYv?J-|e3X{}>-@^KZ1M%wG*PUlSx_zuFbI8RCiXt*o+dkI65>
zpRqce{RH4IaRW$j61nuo$o+f;cuvj}bQq9Q&BJ*?0(M|Te5QPHRdG;$=7(@{fO$$2
z2lxYzzR@Le6G!gEj$rhjPx}nFpIexoQbuhcS4(BT3&Jk4<<(lzRe69#$jW34l6qFJ
z#9z4^A|DTJX9qpRJ2-K_0$iY+g90S9roAH#79sS0K8a8?-!FctOG&x{`~8{h@99S8
zZxO8G!N?2bocc7)r$$i?s_{sv!*%iK6Q<_<fAs){^Js-|7iz=f3$)(f^0shsZ1BAt
zC~}}hTy3qLSh>y3#(2R&$;r^sz0kedsC{9ls@v1Hidc~p!WObA>nC&*&{o6+tT*uC
z7f`ZK?z#99j5o~ku3Y!&7MAR$?~bY$u82I_^3AV<bq%m=l+lEsAZX2Q#X{J>H2{OG
zg;+ho)U*mKp<L<<REQVLf1CA`TT0s7U-5ZS)VJh%^MttaG5IeLIl~bC>o0#@Te@}y
zpt10hcGUoZ(NVXV$9+}fm|7xlvCm_$N)lC#3$tPnj&95Rlay}}0KIDH<sNuMA8)jF
zl+dPP{BNuFy{Eb=MI)xI?^}2W2aJ9`$h0B^Pc(3VYj}Xh4^DO&4N54!-<NY*9?O^9
z&6^3&ZkEtdekv37Cs+FwnTBPq5TtmaHgv@!nJJh>ICI<B4y>Zg+J>?afhBRkBLCDC
zv#*-#sGVFLoDu<aRGtEm^P=sFqdxq4I`{YwjMfZ3d^2LAH{8;juubdk$r5T2PuO}O
zd^53C4(&zxj%e$&h$56-E6K$FvrLjz*=+0K?-c=Pf3Uc_&J|B%+x5_L<-P<HEeZJj
z9mmLiS;L%1K+Q=@7YtM<rGiUhXUI|nhZ{ugMzJ1d0#s~ccm0(+ux`sTzFPkE^G^Vg
z6WSl{0Lp@h-?jazuszEs>mU>NKZg}^tf*w{<L{GM6|zpGh}Tf*+l9Eu5L&b_q#Kmt
zeWl5up3Geb>&0S9H68~o%v}xH7S<e{j!6>eNtW=|)`(g@<YKt)=rOM43?rRDaIdUh
z*r+$~BfsRf%<6gV&rN^1L+(ie_?|8Zx&FR>`DWjxUTziP@<o54*=tFQ=at@1Evm`d
zE6O%FngV}CE^=B36U39zC1Fy1GxB+W*<+YG#!^DgEi8*(DC)<WVpUL9V%*n1pi!$e
zi+(GRWR4*K&YEcly5<LbgA$y3%H>xiQyq7q#KDVl+L=D=7(arPIMzco%uCmbn8;>t
zyDX@CWxm2KVmx6k8w4k0*RE!P2tg6j@!t9ufC1d0tVEL@bsdnjKhTFL8xPRrO4pBf
zx))!`BXQtm2mUQ*OJeB`)K!EwjjG0VZ;&;h`BE-=GMAAff^NRwr$MV$#0pl*bK&rS
zW??{BJb$Sfx3Yt-a;l7m2e^^C?FkGg<@)Q2^+w9fjS#?B+{pZIzf>)s-K3uSlg(Pc
z5P1nen9Z%%OOs(cQ^wZQ5XGUK!1lUa{Dog(rMOW4_?us_SJf#4YLRMCTXdkV2J}Ch
z2F^ZSI8Xd8kuDu$3?1|iwPu04JO`p}EVt~bTgMUPv%rTT{grA!qB}>)P;Z6R&+&Wl
z(?d+dgT%qe`~#RHCayf>mpbvq0VA6i|2E)sRPOJlwF8eOckpAy#GtTVlVB?o4#07G
z8#l^bwHY$8!!a|uKs|G`>JEAZni5n#V*0coW<OJd9cq1pvJPM!)IiFCL5<pMgTg=$
z{LbcaVD-zL|C`!=F&y1<V*5*^L+>}{6A}$nN=?xcJ0<Nt4xDCS4iXP+BEa>C{2AKT
zdaJfLE`kS&3w5SI9_uO0yKk&T!&#3;4?S;R+Rwg<Mzi5LO6hLm6!i?{shqX=Z($_2
zijt8$1~|8dd13$-wfnQ2x<G%w(IDeua|q{yU5M%wC*VpBWp*0OdrLKqo(|Nw9A=YE
zrXfF-2bcp$cGf`Jqi4+XG=1`tJ=$N_rwq$~lwDKu5OLiYlMatKP7S$^?0Q*5QfpzK
zoa~eqI^O0y(%t$u(2S^;SYk#Ao6qJr1GNtXkH2kJxu}n@C@@muIKMx5=dXh&#l|+O
z6L^oR&N9Xq@pI%^sYDY4PKTp6rbI>PFl~9w&xT${cigIKmIwzo<<Z?q8H1W_W{_@w
zBTM>fjJvMSuTL`0JvqCv=NWp1>@m<~6E;J{aG@d~*bdg)@9zLUfSZwu{;^|&H%%hJ
zkLA0S59fQa=sON*-JNrIcBmu7gvhPHerTLSI$BEWqkol5Ryui*Y+>i5i)NOx6J~3x
zu(T9=bj)sk28u0aSG`t7sq+D;C=KU$Qre{h(;5q1B5P25AN*T?@p~jC3rrG3_!7Zf
zV&0Kg-SqxFaLU2P46SSCDhay<ZKG-=xKYo^MeCZ~a2?_c43R6+?c}`&Z>w*ngs%#y
zWG&!m`N5EOVQB2a>6w_DqyjB0JYzoM?>c5?J{XB_F4ll{%&N2q_TVFjT1DE`vYqAi
z28gXHN8vzd^V@GRX84>D_DQd3^ju8HeF&aretb?5q6@xtfxTr<9G$IGdEQ?}4auIS
zr?UVn2$ZQ6fBdOkTz6*(t^YV5duV0@m%NIk(nr|mgULr$lY+@iy*>o&7kMxC4l#*#
z`tTHGxm;s{G$puw$saFMPL10(S@CLp>$9kim7u^%*eGYYl@H$iHr`y@8Sq#X957BD
z+oa_*kggrJimB|ZcjHko`A$oE%xv#AG?QGErWv3coy?T(a~mqbcbS;zt?R3Oebl)w
z)&r6ce0{ORdmGh4V>}H^d14R^|4bE>?<wMev#&f@uUmQwzcQv{QalY!T%B#d>D&wY
zVP>DYJMnh$V9u|hSR<9I1#zfzXtBtz{CWXSN<v_i{$I-ts<X%hI)yt8`!RO^YZ3x(
M8(G|{GjM<UKXCMpga7~l

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1900
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

diff --git a/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3918
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

diff --git a/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..8abe93867390c4047fcfbdc275e725434fd7f57e
GIT binary patch
literal 12140
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77f9f036a47549d47db79c16788749dca10..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2884
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

diff --git a/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f5083623b375139afb391af71cc533a7dd37..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 5914
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

diff --git a/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..b7be5d464e06ab7f7797df2b8ce122e72b588f21
GIT binary patch
literal 18036
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

literal 0
HcmV?d00001

diff --git a/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d6427e6fa1074b79ccd52ef67ac15c5637e85..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3844
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
G0001O&w8c+

diff --git a/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/dataconnect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae37cbc3587421d6889eadd1d91fbf1994d4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7778
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


From 6bb29881c84e34b2f27d0c91b1987ff3a8e20ff4 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 5 Jun 2024 22:37:49 +0100
Subject: [PATCH 10/63] add top 10 movies and latest to the MoviesScreen

---
 .../example/dataconnect/MainActivity.kt       |  5 +-
 .../dataconnect/data/DataConnectMappers.kt    | 24 ++++++++
 .../{DataRepository.kt => MovieRepository.kt} | 13 +++-
 .../feature/movies/MoviesScreen.kt            | 60 ++++++++++++++++---
 .../feature/movies/MoviesUIState.kt           | 15 +++++
 .../feature/movies/MoviesViewModel.kt         | 19 +++---
 .../app/src/main/res/values/strings.xml       |  4 ++
 7 files changed, 120 insertions(+), 20 deletions(-)
 rename dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/{DataRepository.kt => MovieRepository.kt} (54%)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index cccc1901f..5c8f6c15c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
 import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
 import androidx.compose.material.icons.filled.Menu
@@ -19,6 +20,7 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
 import androidx.navigation.NavDestination
 import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -96,7 +98,8 @@ class MainActivity : ComponentActivity() {
                     NavHost(
                         navController,
                         startDestination = MOVIES_ROUTE,
-                        Modifier.consumeWindowInsets(innerPadding),
+                        Modifier.padding(innerPadding)
+                            .consumeWindowInsets(innerPadding),
                     ) {
                         moviesScreen()
                         genresScreen()
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
index 6d80d4da9..eacd17fc5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
@@ -1,6 +1,8 @@
 package com.google.firebase.example.dataconnect.data
 
 import com.google.firebase.dataconnect.movies.ListMoviesQuery
+import com.google.firebase.dataconnect.movies.MoviesRecentlyReleasedQuery
+import com.google.firebase.dataconnect.movies.MoviesTop10Query
 
 fun ListMoviesQuery.Data.MoviesItem.toMovie(): Movie {
     return Movie(
@@ -12,4 +14,26 @@ fun ListMoviesQuery.Data.MoviesItem.toMovie(): Movie {
         rating = this.rating,
         tags = this.tags
     )
+}
+
+fun MoviesTop10Query.Data.MoviesItem.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        imageUrl = this.imageUrl,
+        rating = this.rating,
+        genre = this.genre,
+        tags = this.tags
+    )
+}
+
+fun MoviesRecentlyReleasedQuery.Data.MoviesItem.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        imageUrl = this.imageUrl,
+        rating = this.rating,
+        genre = this.genre,
+        tags = this.tags
+    )
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
similarity index 54%
rename from dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt
rename to dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
index 1b23f1ec2..f7bd483b7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataRepository.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
@@ -4,11 +4,22 @@ import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
 
-class DataRepository(
+class MovieRepository(
     private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) {
 
+    // Repositories
     suspend fun listMovies(): List<Movie> {
         return moviesConnector.listMovies.execute().data.movies.map { it.toMovie() }
     }
+
+    suspend fun getTop10Movies(): List<Movie> {
+        return moviesConnector.moviesTop10.execute().data.movies.map { it.toMovie() }
+    }
+
+    suspend fun getRecentlyReleasedMovies(): List<Movie> {
+        return moviesConnector.moviesRecentlyReleased.execute().data.movies.map { it.toMovie() }
+    }
+
+    // Mutations
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index ef63d19ed..f83e4d526 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -1,43 +1,85 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.Card
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
+import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.data.Movie
 
 @Composable
 fun MoviesScreen(
     moviesViewModel: MoviesViewModel = viewModel()
 ) {
-    val movies by moviesViewModel.movies.collectAsState()
+    val movies by moviesViewModel.uiState.collectAsState()
     MoviesScreen(movies)
 }
 
 @Composable
 fun MoviesScreen(
+    uiState: MoviesUIState
+) {
+    when (uiState) {
+        MoviesUIState.Loading -> {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier.fillMaxSize()
+            ) {
+                CircularProgressIndicator()
+            }
+        }
+        is MoviesUIState.Error -> {
+            Text(uiState.errorMessage)
+        }
+        is MoviesUIState.Success -> {
+            Column(
+                modifier = Modifier.padding(16.dp)
+            ) {
+                Text(
+                    text = stringResource(R.string.title_top_10_movies),
+                    style = MaterialTheme.typography.headlineMedium,
+                    modifier = Modifier.padding(bottom = 16.dp)
+                )
+                HorizontalMovieList(uiState.top10movies)
+                Text(
+                    text = stringResource(R.string.title_latest_movies),
+                    style = MaterialTheme.typography.headlineMedium,
+                    modifier = Modifier.padding(vertical = 16.dp)
+                )
+                HorizontalMovieList(uiState.latestMovies)
+            }
+        }
+    }
+}
+
+@Composable
+fun HorizontalMovieList(
     movies: List<Movie>
 ) {
-    LazyVerticalGrid(
-        columns = GridCells.Fixed(3),
-        modifier = Modifier.padding(top = 48.dp)
-    ) {
+    LazyRow {
         items(movies) { movie ->
             Card(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(8.dp),
+                modifier = Modifier.padding(8.dp)
+                    .fillParentMaxWidth(0.3f),
             ) {
                 AsyncImage(
                     model = movie.imageUrl,
@@ -56,4 +98,4 @@ fun MoviesScreen(
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
new file mode 100644
index 000000000..73aca9a07
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
@@ -0,0 +1,15 @@
+package com.google.firebase.example.dataconnect.feature.movies
+
+import com.google.firebase.example.dataconnect.data.Movie
+
+sealed class MoviesUIState {
+
+    data object Loading: MoviesUIState()
+
+    data class Error(val errorMessage: String): MoviesUIState()
+
+    data class Success(
+        val top10movies: List<Movie>,
+        val latestMovies: List<Movie>
+    ) : MoviesUIState()
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
index 175286962..09fe510bd 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
@@ -1,28 +1,29 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
-import androidx.compose.runtime.Composable
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.google.firebase.example.dataconnect.data.DataRepository
-import com.google.firebase.example.dataconnect.data.Movie
+import com.google.firebase.example.dataconnect.data.MovieRepository
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class MoviesViewModel(
-    private val dataRepository: DataRepository = DataRepository()
+    private val dataRepository: MovieRepository = MovieRepository()
 ) : ViewModel() {
 
-    private val _movies = MutableStateFlow<List<Movie>>(emptyList())
-    val movies: StateFlow<List<Movie>>
-        get() = _movies
+    private val _uiState = MutableStateFlow<MoviesUIState>(MoviesUIState.Loading)
+    val uiState: StateFlow<MoviesUIState>
+        get() = _uiState
 
     init {
         viewModelScope.launch {
             try {
-                _movies.value = dataRepository.listMovies()
+                val top10Movies = dataRepository.getTop10Movies()
+                val latestMovies = dataRepository.getRecentlyReleasedMovies()
+
+                _uiState.value = MoviesUIState.Success(top10Movies, latestMovies)
             } catch (e: Exception) {
-                e.printStackTrace()
+                _uiState.value = MoviesUIState.Error(e.localizedMessage)
             }
         }
     }
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index a2cda77fb..881ef05a7 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -6,4 +6,8 @@
     <string name="label_genres">Genres</string>
     <string name="label_search">Search</string>
     <string name="label_profile">Profile</string>
+
+    <!-- Movies Screen -->
+    <string name="title_top_10_movies">Top 10 Movies</string>
+    <string name="title_latest_movies">Latest Movies</string>
 </resources>
\ No newline at end of file

From 84bf1b0e0456f2a23b26eae33e298e6325a98f4a Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 5 Jun 2024 22:51:07 +0100
Subject: [PATCH 11/63] create GenresScreen

---
 .../example/dataconnect/MainActivity.kt       | 20 ++++-------
 .../feature/genres/GenresScreen.kt            | 35 +++++++++++++++++++
 .../dataconnect/feature/genres/Navigation.kt  |  2 +-
 3 files changed, 43 insertions(+), 14 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index 5c8f6c15c..db91c05a3 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -29,12 +29,15 @@ import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
 import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
 import com.google.firebase.example.dataconnect.feature.genres.genresScreen
+import com.google.firebase.example.dataconnect.feature.genres.navigateToGenres
 import com.google.firebase.example.dataconnect.feature.movies.MOVIES_ROUTE
 import com.google.firebase.example.dataconnect.feature.movies.moviesScreen
 import com.google.firebase.example.dataconnect.feature.movies.navigateToMovies
 import com.google.firebase.example.dataconnect.feature.profile.PROFILE_ROUTE
+import com.google.firebase.example.dataconnect.feature.profile.navigateToProfile
 import com.google.firebase.example.dataconnect.feature.profile.profileScreen
 import com.google.firebase.example.dataconnect.feature.search.SEARCH_ROUTE
+import com.google.firebase.example.dataconnect.feature.search.navigateToSearch
 import com.google.firebase.example.dataconnect.feature.search.searchScreen
 import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
 
@@ -56,19 +59,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_movies)) },
                                 selected = isRouteSelected(currentDestination, MOVIES_ROUTE),
                                 onClick = {
-                                    navController.navigateToMovies {
-                                        // Pop up to the start destination of the graph to
-                                        // avoid building up a large stack of destinations
-                                        // on the back stack as users select items
-                                        popUpTo(navController.graph.findStartDestination().id) {
-                                            saveState = true
-                                        }
-                                        // Avoid multiple copies of the same destination when
-                                        // reselecting the same item
-                                        launchSingleTop = true
-                                        // Restore state when reselecting a previously selected item
-                                        restoreState = true
-                                    }
+                                    navController.navigateToMovies { }
                                 }
                             )
                             NavigationBarItem(
@@ -76,6 +67,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_genres)) },
                                 selected = isRouteSelected(currentDestination, GENRES_ROUTE),
                                 onClick = {
+                                    navController.navigateToGenres { }
                                 }
                             )
                             NavigationBarItem(
@@ -83,6 +75,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_search)) },
                                 selected = isRouteSelected(currentDestination, SEARCH_ROUTE),
                                 onClick = {
+                                    navController.navigateToSearch {  }
                                 }
                             )
                             NavigationBarItem(
@@ -90,6 +83,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_profile)) },
                                 selected = isRouteSelected(currentDestination, PROFILE_ROUTE),
                                 onClick = {
+                                    navController.navigateToProfile {  }
                                 }
                             )
                         }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
new file mode 100644
index 000000000..f182e50c0
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
@@ -0,0 +1,35 @@
+package com.google.firebase.example.dataconnect.feature.genres
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+
+@Composable
+fun GenresScreen() {
+    // Hardcoding genres for now
+    val genres = arrayOf("Action", "Crime", "Drama", "Sci-Fi")
+
+    LazyColumn {
+        items(genres) { genre ->
+            Card(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(vertical = 8.dp, horizontal = 16.dp)
+            ) {
+                Text(
+                    text = genre,
+                    style = MaterialTheme.typography.headlineMedium,
+                    modifier = Modifier.padding(8.dp)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
index 686c9efbb..4f9d5f27d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
@@ -14,7 +14,7 @@ fun NavGraphBuilder.genresScreen(
 
 ) {
     composable(route = GENRES_ROUTE) {
-        // TODO: Call composable
+        GenresScreen()
     }
 }
 

From 0b156e24ee9a817d309f33e6e90455bbf59602dc Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 5 Jun 2024 23:37:39 +0100
Subject: [PATCH 12/63] create GenreDetailScreen

---
 .../example/dataconnect/MainActivity.kt       | 21 +++--
 .../dataconnect/data/DataConnectMappers.kt    | 25 +++++-
 .../dataconnect/data/MovieRepository.kt       |  7 ++
 .../example/dataconnect/data/MoviesByGenre.kt |  6 ++
 .../feature/genredetail/GenreDetailScreen.kt  | 78 +++++++++++++++++++
 .../feature/genredetail/GenreDetailUIState.kt | 16 ++++
 .../genredetail/GenreDetailViewModel.kt       | 34 ++++++++
 .../feature/genredetail/Navigation.kt         | 27 +++++++
 .../feature/genres/GenresScreen.kt            |  8 +-
 .../dataconnect/feature/genres/Navigation.kt  |  6 +-
 .../feature/movies/MoviesScreen.kt            | 14 +++-
 .../app/src/main/res/values/strings.xml       |  5 ++
 12 files changed, 233 insertions(+), 14 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index db91c05a3..b8152989c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -27,6 +27,8 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
+import com.google.firebase.example.dataconnect.feature.genredetail.genreDetailScreen
+import com.google.firebase.example.dataconnect.feature.genredetail.navigateToGenreDetail
 import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
 import com.google.firebase.example.dataconnect.feature.genres.genresScreen
 import com.google.firebase.example.dataconnect.feature.genres.navigateToGenres
@@ -59,7 +61,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_movies)) },
                                 selected = isRouteSelected(currentDestination, MOVIES_ROUTE),
                                 onClick = {
-                                    navController.navigateToMovies { }
+                                    navController.navigateToMovies { launchSingleTop = true }
                                 }
                             )
                             NavigationBarItem(
@@ -67,7 +69,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_genres)) },
                                 selected = isRouteSelected(currentDestination, GENRES_ROUTE),
                                 onClick = {
-                                    navController.navigateToGenres { }
+                                    navController.navigateToGenres { launchSingleTop = true }
                                 }
                             )
                             NavigationBarItem(
@@ -75,7 +77,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_search)) },
                                 selected = isRouteSelected(currentDestination, SEARCH_ROUTE),
                                 onClick = {
-                                    navController.navigateToSearch {  }
+                                    navController.navigateToSearch { launchSingleTop = true }
                                 }
                             )
                             NavigationBarItem(
@@ -83,7 +85,7 @@ class MainActivity : ComponentActivity() {
                                 label = { Text(stringResource(R.string.label_profile)) },
                                 selected = isRouteSelected(currentDestination, PROFILE_ROUTE),
                                 onClick = {
-                                    navController.navigateToProfile {  }
+                                    navController.navigateToProfile { launchSingleTop = true }
                                 }
                             )
                         }
@@ -96,7 +98,14 @@ class MainActivity : ComponentActivity() {
                             .consumeWindowInsets(innerPadding),
                     ) {
                         moviesScreen()
-                        genresScreen()
+                        genresScreen(
+                            onGenreClicked = { genre ->
+                                navController.navigateToGenreDetail(genre) {
+                                    launchSingleTop = true
+                                }
+                            }
+                        )
+                        genreDetailScreen()
                         searchScreen()
                         profileScreen()
                     }
@@ -107,4 +116,4 @@ class MainActivity : ComponentActivity() {
 }
 
 private fun isRouteSelected(currentDestination: NavDestination?, route: String) =
-    currentDestination?.hierarchy?.any { it.route == route } == true
+    currentDestination?.hierarchy?.any { it.route?.startsWith(route) ?: false } == true
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
index eacd17fc5..288a7246e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
@@ -1,5 +1,6 @@
 package com.google.firebase.example.dataconnect.data
 
+import com.google.firebase.dataconnect.movies.ListMoviesByGenreQuery
 import com.google.firebase.dataconnect.movies.ListMoviesQuery
 import com.google.firebase.dataconnect.movies.MoviesRecentlyReleasedQuery
 import com.google.firebase.dataconnect.movies.MoviesTop10Query
@@ -36,4 +37,26 @@ fun MoviesRecentlyReleasedQuery.Data.MoviesItem.toMovie(): Movie {
         genre = this.genre,
         tags = this.tags
     )
-}
\ No newline at end of file
+}
+
+fun ListMoviesByGenreQuery.Data.MostRecentItem.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        imageUrl = this.imageUrl,
+        rating = this.rating,
+        tags = this.tags
+    )
+}
+
+fun ListMoviesByGenreQuery.Data.MostPopularItem.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        imageUrl = this.imageUrl,
+        rating = this.rating,
+        tags = this.tags
+    )
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
index f7bd483b7..cc382dfb5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
@@ -21,5 +21,12 @@ class MovieRepository(
         return moviesConnector.moviesRecentlyReleased.execute().data.movies.map { it.toMovie() }
     }
 
+    suspend fun getMoviesByGenre(genre: String): MoviesByGenre {
+        val data = moviesConnector.listMoviesByGenre.execute(genre).data
+        val mostPopular = data.mostPopular.map { it.toMovie() }
+        val mostRecent = data.mostRecent.map { it.toMovie() }
+        return MoviesByGenre(mostPopular, mostRecent)
+    }
+
     // Mutations
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt
new file mode 100644
index 000000000..e26e14a62
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt
@@ -0,0 +1,6 @@
+package com.google.firebase.example.dataconnect.data
+
+data class MoviesByGenre(
+    val mostPopular: List<Movie>,
+    val mostRecent: List<Movie>
+)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
new file mode 100644
index 000000000..1313174c2
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -0,0 +1,78 @@
+package com.google.firebase.example.dataconnect.feature.genredetail
+
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.feature.movies.HorizontalMovieList
+
+@Composable
+fun GenreDetailScreen(
+    genre: String,
+    moviesViewModel: GenreDetailViewModel = viewModel()
+) {
+    moviesViewModel.setGenre(genre)
+    val movies by moviesViewModel.uiState.collectAsState()
+    GenreDetailScreen(movies)
+}
+
+@Composable
+fun GenreDetailScreen(
+    uiState: GenreDetailUIState
+) {
+    when (uiState) {
+        GenreDetailUIState.Loading -> {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier.fillMaxSize()
+            ) {
+                CircularProgressIndicator()
+            }
+        }
+        is GenreDetailUIState.Error -> {
+            Text(uiState.errorMessage)
+        }
+        is GenreDetailUIState.Success -> {
+            val scrollState = rememberScrollState()
+            Column(
+                modifier = Modifier
+                    .padding(16.dp)
+                    .verticalScroll(scrollState)
+            ) {
+                Text(
+                    text = stringResource(R.string.title_genre_detail, uiState.genreName),
+                    style = MaterialTheme.typography.headlineLarge
+                )
+                Text(
+                    text = stringResource(R.string.title_most_popular),
+                    style = MaterialTheme.typography.headlineMedium,
+                    modifier = Modifier.padding(vertical = 16.dp)
+                )
+                HorizontalMovieList(uiState.moviesByGenre.mostPopular)
+                Text(
+                    text = stringResource(R.string.title_most_recent),
+                    style = MaterialTheme.typography.headlineMedium,
+                    modifier = Modifier.padding(vertical = 16.dp)
+                )
+                HorizontalMovieList(uiState.moviesByGenre.mostRecent)
+            }
+        }
+    }
+}
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
new file mode 100644
index 000000000..b4b99178e
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
@@ -0,0 +1,16 @@
+package com.google.firebase.example.dataconnect.feature.genredetail
+
+import com.google.firebase.example.dataconnect.data.Movie
+import com.google.firebase.example.dataconnect.data.MoviesByGenre
+
+sealed class GenreDetailUIState {
+
+    data object Loading: GenreDetailUIState()
+
+    data class Error(val errorMessage: String): GenreDetailUIState()
+
+    data class Success(
+        val genreName: String,
+        val moviesByGenre: MoviesByGenre
+    ) : GenreDetailUIState()
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
new file mode 100644
index 000000000..912e11540
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
@@ -0,0 +1,34 @@
+package com.google.firebase.example.dataconnect.feature.genredetail
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.firebase.example.dataconnect.data.MovieRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class GenreDetailViewModel(
+    private val dataRepository: MovieRepository = MovieRepository()
+) : ViewModel() {
+    private var genre = ""
+
+    private val _uiState = MutableStateFlow<GenreDetailUIState>(GenreDetailUIState.Loading)
+    val uiState: StateFlow<GenreDetailUIState>
+        get() = _uiState
+
+    // TODO(thatfiredev): Create a ViewModelFactory to set genre
+    fun setGenre(genre: String) {
+        this.genre = genre
+        viewModelScope.launch {
+            try {
+                val movies = dataRepository.getMoviesByGenre(genre.lowercase())
+                _uiState.value = GenreDetailUIState.Success(
+                    genreName = genre,
+                    moviesByGenre = movies
+                )
+            } catch (e: Exception) {
+                _uiState.value = GenreDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt
new file mode 100644
index 000000000..45edf83b3
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt
@@ -0,0 +1,27 @@
+package com.google.firebase.example.dataconnect.feature.genredetail
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.navArgument
+
+const val GENRE_DETAIL_ROUTE = "genres/{genre}"
+
+fun NavController.navigateToGenreDetail(
+    genre: String,
+    navOptions: NavOptionsBuilder.() -> Unit = { }
+) = navigate(GENRE_DETAIL_ROUTE.replace("{genre}", genre), navOptions)
+
+fun NavGraphBuilder.genreDetailScreen() {
+    composable(
+        route = GENRE_DETAIL_ROUTE
+    ) { backStackEntry ->
+        backStackEntry.arguments?.let {
+            GenreDetailScreen(it.getString("genre", "Action"))
+        }
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
index f182e50c0..4bcd6bf40 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
@@ -1,5 +1,6 @@
 package com.google.firebase.example.dataconnect.feature.genres
 
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
@@ -13,7 +14,9 @@ import androidx.compose.ui.unit.dp
 
 
 @Composable
-fun GenresScreen() {
+fun GenresScreen(
+    onGenreClicked: (genre: String) -> Unit = {}
+) {
     // Hardcoding genres for now
     val genres = arrayOf("Action", "Crime", "Drama", "Sci-Fi")
 
@@ -23,6 +26,9 @@ fun GenresScreen() {
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(vertical = 8.dp, horizontal = 16.dp)
+                    .clickable {
+                        onGenreClicked(genre)
+                    }
             ) {
                 Text(
                     text = genre,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
index 4f9d5f27d..f2fce931a 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
@@ -5,16 +5,16 @@ import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavOptionsBuilder
 import androidx.navigation.compose.composable
 
-const val GENRES_ROUTE = "genres_route"
+const val GENRES_ROUTE = "genres"
 
 fun NavController.navigateToGenres(navOptions: NavOptionsBuilder.() -> Unit) =
     navigate(GENRES_ROUTE, navOptions)
 
 fun NavGraphBuilder.genresScreen(
-
+    onGenreClicked: (genre: String) -> Unit
 ) {
     composable(route = GENRES_ROUTE) {
-        GenresScreen()
+        GenresScreen(onGenreClicked)
     }
 }
 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index f83e4d526..1da620796 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -10,6 +10,8 @@ import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.items
 import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Card
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
@@ -19,6 +21,7 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
@@ -51,8 +54,10 @@ fun MoviesScreen(
             Text(uiState.errorMessage)
         }
         is MoviesUIState.Success -> {
+            val scrollState = rememberScrollState()
             Column(
                 modifier = Modifier.padding(16.dp)
+                    .verticalScroll(scrollState)
             ) {
                 Text(
                     text = stringResource(R.string.title_top_10_movies),
@@ -78,22 +83,25 @@ fun HorizontalMovieList(
     LazyRow {
         items(movies) { movie ->
             Card(
-                modifier = Modifier.padding(8.dp)
+                modifier = Modifier
+                    .padding(4.dp)
                     .fillParentMaxWidth(0.3f),
             ) {
                 AsyncImage(
                     model = movie.imageUrl,
                     contentDescription = null,
+                    contentScale = ContentScale.Crop,
                     modifier = Modifier.fillMaxWidth()
                 )
                 Text(
                     text = movie.title,
                     style = MaterialTheme.typography.titleMedium,
-                    modifier = Modifier.padding(8.dp)
+                    modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp)
                 )
                 Text(
                     text = "Rating: ${movie.rating}",
-                    modifier = Modifier.padding(bottom = 8.dp, start = 8.dp)
+                    modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
+                    style = MaterialTheme.typography.bodySmall
                 )
             }
         }
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index 881ef05a7..d34dbccb6 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -10,4 +10,9 @@
     <!-- Movies Screen -->
     <string name="title_top_10_movies">Top 10 Movies</string>
     <string name="title_latest_movies">Latest Movies</string>
+
+    <!-- Genre Detail Title -->
+    <string name="title_genre_detail">%s Movies</string>
+    <string name="title_most_popular">Most Popular</string>
+    <string name="title_most_recent">Most Recent</string>
 </resources>
\ No newline at end of file

From d288623ce2ad75f817c613d014000d56d40b5f3f Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 7 Jun 2024 16:06:59 +0100
Subject: [PATCH 13/63] create MovieDetailScreen

---
 .../example/dataconnect/MainActivity.kt       |  22 +--
 .../dataconnect/data/DataConnectMappers.kt    |  13 +-
 .../example/dataconnect/data/Movie.kt         |   2 +-
 .../dataconnect/data/MovieRepository.kt       |   6 +
 .../feature/genredetail/GenreDetailScreen.kt  |  14 +-
 .../feature/moviedetail/MovieDetailScreen.kt  | 127 ++++++++++++++++++
 .../feature/moviedetail/MovieDetailUIState.kt |  14 ++
 .../moviedetail/MovieDetailViewModel.kt       |  30 +++++
 .../feature/moviedetail/Navigation.kt         |  29 ++++
 .../feature/movies/MoviesScreen.kt            |  22 +--
 .../dataconnect/feature/movies/Navigation.kt  |   6 +-
 .../app/src/main/res/values/strings.xml       |   6 +-
 12 files changed, 266 insertions(+), 25 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index b8152989c..a0d8bdd30 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -32,6 +32,8 @@ import com.google.firebase.example.dataconnect.feature.genredetail.navigateToGen
 import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
 import com.google.firebase.example.dataconnect.feature.genres.genresScreen
 import com.google.firebase.example.dataconnect.feature.genres.navigateToGenres
+import com.google.firebase.example.dataconnect.feature.moviedetail.movieDetailScreen
+import com.google.firebase.example.dataconnect.feature.moviedetail.navigateToMovieDetail
 import com.google.firebase.example.dataconnect.feature.movies.MOVIES_ROUTE
 import com.google.firebase.example.dataconnect.feature.movies.moviesScreen
 import com.google.firebase.example.dataconnect.feature.movies.navigateToMovies
@@ -94,17 +96,21 @@ class MainActivity : ComponentActivity() {
                     NavHost(
                         navController,
                         startDestination = MOVIES_ROUTE,
-                        Modifier.padding(innerPadding)
+                        Modifier
+                            .padding(innerPadding)
                             .consumeWindowInsets(innerPadding),
                     ) {
-                        moviesScreen()
-                        genresScreen(
-                            onGenreClicked = { genre ->
-                                navController.navigateToGenreDetail(genre) {
-                                    launchSingleTop = true
-                                }
+                        moviesScreen(onMovieClicked = { movieId ->
+                            navController.navigateToMovieDetail(movieId) {
+                                launchSingleTop = true
+                            }
+                        })
+                        movieDetailScreen()
+                        genresScreen(onGenreClicked = { genre ->
+                            navController.navigateToGenreDetail(genre) {
+                                launchSingleTop = true
                             }
-                        )
+                        })
                         genreDetailScreen()
                         searchScreen()
                         profileScreen()
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
index 288a7246e..8dc63c5e5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
@@ -1,5 +1,6 @@
 package com.google.firebase.example.dataconnect.data
 
+import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.dataconnect.movies.ListMoviesByGenreQuery
 import com.google.firebase.dataconnect.movies.ListMoviesQuery
 import com.google.firebase.dataconnect.movies.MoviesRecentlyReleasedQuery
@@ -59,4 +60,14 @@ fun ListMoviesByGenreQuery.Data.MostPopularItem.toMovie(): Movie {
     )
 }
 
-
+fun GetMovieByIdQuery.Data.Movie.toMovie(): Movie {
+    return Movie(
+        id = this.id,
+        title = this.title,
+        description = this.description,
+        imageUrl = this.imageUrl,
+        rating = this.rating,
+        genre = this.genre,
+        tags = this.tags
+    )
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
index c4466547c..bbd08c19b 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
@@ -9,6 +9,6 @@ data class Movie(
     val releaseYear: Int? = 1970,
     val genre: String? = "",
     val rating: Double? = 0.0,
-    val description: String? = "",
+    val description: String? = null,
     val tags: List<String?>? = emptyList()
 )
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
index cc382dfb5..00dad663c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
@@ -3,6 +3,7 @@ package com.google.firebase.example.dataconnect.data
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
+import java.util.UUID
 
 class MovieRepository(
     private val moviesConnector: MoviesConnector = MoviesConnector.instance
@@ -28,5 +29,10 @@ class MovieRepository(
         return MoviesByGenre(mostPopular, mostRecent)
     }
 
+    suspend fun getMovieByID(movieID: String): Movie? {
+        val id = UUID.fromString(movieID)
+        return moviesConnector.getMovieById.execute(id).data.movie?.toMovie()
+    }
+
     // Mutations
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 1313174c2..40808c519 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -64,13 +64,23 @@ fun GenreDetailScreen(
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(uiState.moviesByGenre.mostPopular)
+                HorizontalMovieList(
+                    uiState.moviesByGenre.mostPopular,
+                    onMovieClicked = {
+                        // TODO(thatfiredev)
+                    }
+                )
                 Text(
                     text = stringResource(R.string.title_most_recent),
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(uiState.moviesByGenre.mostRecent)
+                HorizontalMovieList(
+                    uiState.moviesByGenre.mostRecent,
+                    onMovieClicked = {
+                        // TODO(thatfiredev)
+                    }
+                )
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
new file mode 100644
index 000000000..f43463d2f
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -0,0 +1,127 @@
+package com.google.firebase.example.dataconnect.feature.moviedetail
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.AsyncImage
+import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.data.Movie
+
+@Composable
+fun MovieDetailScreen(
+    movieId: String,
+    movieDetailViewModel: MovieDetailViewModel = viewModel()
+) {
+    movieDetailViewModel.setMovieId(movieId)
+    val uiState by movieDetailViewModel.uiState.collectAsState()
+    MovieDetailScreen(uiState)
+}
+
+@Composable
+fun MovieDetailScreen(
+    uiState: MovieDetailUIState
+) {
+    when (uiState) {
+        is MovieDetailUIState.Error -> {
+            ErrorMessage(uiState.errorMessage)
+        }
+
+        MovieDetailUIState.Loading -> {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier.fillMaxSize()
+            ) {
+                CircularProgressIndicator()
+            }
+        }
+
+        is MovieDetailUIState.Success -> {
+            val movie = uiState.movie
+            MovieInformation(movie)
+        }
+    }
+}
+
+@Composable
+fun MovieInformation(movie: Movie?) {
+    if (movie == null) {
+        ErrorMessage(stringResource(R.string.error_movie_not_found))
+    } else {
+        val scrollState = rememberScrollState()
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .verticalScroll(scrollState)
+        ) {
+            Text(
+                text = movie.title,
+                style = MaterialTheme.typography.headlineLarge
+            )
+            Row {
+                Text(
+                    text = movie.releaseYear.toString(),
+                    style = MaterialTheme.typography.labelMedium,
+                    modifier = Modifier.padding(end = 4.dp)
+                )
+                Text(
+                    text = movie.rating?.toString() ?: "0.0",
+                    style = MaterialTheme.typography.labelMedium
+                )
+            }
+            Row {
+                AsyncImage(
+                    model = movie.imageUrl,
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,
+                    modifier = Modifier
+                        .padding(vertical = 8.dp)
+                )
+                Column(
+                    modifier = Modifier.padding(horizontal = 8.dp)
+                ) {
+                    Row {
+                        movie.tags?.let { movieTags ->
+                            movieTags.filterNotNull().forEach { tag ->
+                                SuggestionChip(
+                                    onClick = { },
+                                    label = { Text(tag) },
+                                    modifier = Modifier
+                                        .padding(horizontal = 4.dp, vertical = 8.dp)
+                                )
+                            }
+                        }
+                    }
+                    Text(
+                        text = movie.description ?: stringResource(R.string.description_not_available),
+                        modifier = Modifier.fillMaxWidth()
+                    )
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun ErrorMessage(
+    message: String
+) {
+    Text(message)
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
new file mode 100644
index 000000000..685cb73e1
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
@@ -0,0 +1,14 @@
+package com.google.firebase.example.dataconnect.feature.moviedetail
+
+import com.google.firebase.example.dataconnect.data.Movie
+
+sealed class MovieDetailUIState {
+    data object Loading: MovieDetailUIState()
+
+    data class Error(val errorMessage: String): MovieDetailUIState()
+
+    data class Success(
+        // Movie is null if it can't be found on the DB
+        val movie: Movie?
+    ) : MovieDetailUIState()
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
new file mode 100644
index 000000000..7697bce16
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -0,0 +1,30 @@
+package com.google.firebase.example.dataconnect.feature.moviedetail
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.firebase.example.dataconnect.data.MovieRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class MovieDetailViewModel(
+    private val repository: MovieRepository = MovieRepository()
+) : ViewModel() {
+    private var movieId: String = ""
+
+    private val _uiState = MutableStateFlow<MovieDetailUIState>(MovieDetailUIState.Loading)
+    val uiState: StateFlow<MovieDetailUIState>
+        get() = _uiState
+
+    fun setMovieId(id: String) {
+        movieId = id
+        viewModelScope.launch {
+            try {
+                val movie = repository.getMovieByID(movieId)
+                _uiState.value = MovieDetailUIState.Success(movie)
+            } catch (e: Exception) {
+                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
new file mode 100644
index 000000000..b80048200
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
@@ -0,0 +1,29 @@
+package com.google.firebase.example.dataconnect.feature.moviedetail
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val MOVIE_DETAIL_ROUTE = "movies/{movie}"
+
+fun NavController.navigateToMovieDetail(
+    movieId: String,
+    navOptions: NavOptionsBuilder.() -> Unit = { }
+) = navigate(MOVIE_DETAIL_ROUTE.replace("{movie}", movieId), navOptions)
+
+fun NavGraphBuilder.movieDetailScreen() {
+    composable(
+        route = MOVIE_DETAIL_ROUTE
+    ) { backStackEntry ->
+        backStackEntry.arguments?.let {
+            val movieId = it.getString("movie")
+            movieId?.let { id ->
+                MovieDetailScreen(id)
+            }
+
+        }
+    }
+}
+
+
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index 1da620796..1fddeb992 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -1,14 +1,12 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
@@ -31,15 +29,17 @@ import com.google.firebase.example.dataconnect.data.Movie
 
 @Composable
 fun MoviesScreen(
+    onMovieClicked: (movie: String) -> Unit,
     moviesViewModel: MoviesViewModel = viewModel()
 ) {
     val movies by moviesViewModel.uiState.collectAsState()
-    MoviesScreen(movies)
+    MoviesScreen(movies, onMovieClicked)
 }
 
 @Composable
 fun MoviesScreen(
-    uiState: MoviesUIState
+    uiState: MoviesUIState,
+    onMovieClicked: (movie: String) -> Unit
 ) {
     when (uiState) {
         MoviesUIState.Loading -> {
@@ -64,13 +64,13 @@ fun MoviesScreen(
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(bottom = 16.dp)
                 )
-                HorizontalMovieList(uiState.top10movies)
+                HorizontalMovieList(uiState.top10movies, onMovieClicked)
                 Text(
                     text = stringResource(R.string.title_latest_movies),
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(uiState.latestMovies)
+                HorizontalMovieList(uiState.latestMovies, onMovieClicked)
             }
         }
     }
@@ -78,14 +78,18 @@ fun MoviesScreen(
 
 @Composable
 fun HorizontalMovieList(
-    movies: List<Movie>
+    movies: List<Movie>,
+    onMovieClicked: (movie: String) -> Unit
 ) {
     LazyRow {
         items(movies) { movie ->
             Card(
                 modifier = Modifier
                     .padding(4.dp)
-                    .fillParentMaxWidth(0.3f),
+                    .fillParentMaxWidth(0.4f)
+                    .clickable {
+                        onMovieClicked(movie.id.toString())
+                    },
             ) {
                 AsyncImage(
                     model = movie.imageUrl,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
index 5861da685..06c256d04 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
@@ -5,16 +5,16 @@ import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavOptionsBuilder
 import androidx.navigation.compose.composable
 
-const val MOVIES_ROUTE = "movies_route"
+const val MOVIES_ROUTE = "movies"
 
 fun NavController.navigateToMovies(navOptions: NavOptionsBuilder.() -> Unit) =
     navigate(MOVIES_ROUTE, navOptions)
 
 fun NavGraphBuilder.moviesScreen(
-
+    onMovieClicked: (movie: String) -> Unit
 ) {
     composable(route = MOVIES_ROUTE) {
-        MoviesScreen()
+        MoviesScreen(onMovieClicked)
     }
 }
 
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index d34dbccb6..e173c48a7 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -11,8 +11,12 @@
     <string name="title_top_10_movies">Top 10 Movies</string>
     <string name="title_latest_movies">Latest Movies</string>
 
-    <!-- Genre Detail Title -->
+    <!-- Genre Detail Screen -->
     <string name="title_genre_detail">%s Movies</string>
     <string name="title_most_popular">Most Popular</string>
     <string name="title_most_recent">Most Recent</string>
+
+    <!-- Movie Detail Screen -->
+    <string name="error_movie_not_found">Couldnt find movie in the database</string>
+    <string name="description_not_available">Description not available</string>
 </resources>
\ No newline at end of file

From bdcd7e890b3b5a609be794b92d88116d13dbf1f3 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 13 Sep 2024 15:47:43 +0100
Subject: [PATCH 14/63] chore: bump gradle plugins

---
 gradle/libs.versions.toml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5e230d1ca..81dddba11 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,9 +1,9 @@
 [versions]
-agp = "8.4.0"
+agp = "8.6.0"
 coilCompose = "2.6.0"
 firebaseAuth = "23.0.0"
 firebaseDataConnect = "16.0.0-alpha03"
-kotlin = "1.9.23"
+kotlin = "2.0.20"
 coreKtx = "1.13.1"
 junit = "4.13.2"
 junitVersion = "1.1.5"
@@ -11,7 +11,7 @@ espressoCore = "3.5.1"
 lifecycle = "2.8.1"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
-googleServices = "4.4.1"
+googleServices = "4.4.2"
 composeNavigation = "2.7.7"
 
 [libraries]

From 27aa537d2e601bd86bc6cfcc76259bd80737d61f Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 13 Sep 2024 15:48:30 +0100
Subject: [PATCH 15/63] test: delete test modules

---
 .../dataconnect/ExampleInstrumentedTest.kt    | 24 -------------------
 .../example/dataconnect/ExampleUnitTest.kt    | 17 -------------
 2 files changed, 41 deletions(-)
 delete mode 100644 dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
 delete mode 100644 dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt

diff --git a/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt b/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
deleted file mode 100644
index 2ab99c2c8..000000000
--- a/dataconnect/app/src/androidTest/java/com/google/firebase/example/dataconnect/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.google.firebase.example.dataconnect
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.ext.junit.runners.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
-    @Test
-    fun useAppContext() {
-        // Context of the app under test.
-        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
-        assertEquals("com.google.firebase.example.dataconnect", appContext.packageName)
-    }
-}
\ No newline at end of file
diff --git a/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt b/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt
deleted file mode 100644
index 7c4c4ce12..000000000
--- a/dataconnect/app/src/test/java/com/google/firebase/example/dataconnect/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.google.firebase.example.dataconnect
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
-    @Test
-    fun addition_isCorrect() {
-        assertEquals(4, 2 + 2)
-    }
-}
\ No newline at end of file

From 24ca051647cc21ab9b95bcf395c6fbd34ed560cc Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 13 Sep 2024 16:06:56 +0100
Subject: [PATCH 16/63] chore: add compose compiler

---
 build.gradle.kts                                     | 1 +
 dataconnect/app/build.gradle.kts                     | 3 ++-
 dataconnect/build.gradle.kts                         | 3 ++-
 dataconnect/gradle/wrapper/gradle-wrapper.properties | 2 +-
 gradle/libs.versions.toml                            | 1 +
 5 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 01aa79b16..e795bc8d6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,6 +9,7 @@ plugins {
     id("com.google.firebase.firebase-perf") version "1.4.2" apply false
     id("androidx.navigation.safeargs") version "2.8.0" apply false
     id("com.github.ben-manes.versions") version "0.51.0" apply true
+    id("org.jetbrains.kotlin.plugin.compose") version "2.0.20" apply false
 }
 
 allprojects {
diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index ff39640d0..e6d0e400a 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     alias(libs.plugins.jetbrains.kotlin.android)
     alias(libs.plugins.kotlin.serialization)
     alias(libs.plugins.google.services)
+    alias(libs.plugins.compose.compiler)
 }
 
 android {
@@ -80,4 +81,4 @@ dependencies {
     androidTestImplementation(libs.androidx.ui.test.junit4)
     debugImplementation(libs.androidx.ui.tooling)
     debugImplementation(libs.androidx.ui.test.manifest)
-}
\ No newline at end of file
+}
diff --git a/dataconnect/build.gradle.kts b/dataconnect/build.gradle.kts
index 7252638e5..8e1379dd6 100644
--- a/dataconnect/build.gradle.kts
+++ b/dataconnect/build.gradle.kts
@@ -3,4 +3,5 @@ plugins {
     alias(libs.plugins.android.application) apply false
     alias(libs.plugins.jetbrains.kotlin.android) apply false
     alias(libs.plugins.google.services) apply false
-}
\ No newline at end of file
+    alias(libs.plugins.compose.compiler) apply false
+}
diff --git a/dataconnect/gradle/wrapper/gradle-wrapper.properties b/dataconnect/gradle/wrapper/gradle-wrapper.properties
index a048a37a2..367d4dffd 100644
--- a/dataconnect/gradle/wrapper/gradle-wrapper.properties
+++ b/dataconnect/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Wed May 08 19:29:05 BST 2024
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 81dddba11..d1ea661b2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -41,3 +41,4 @@ android-application = { id = "com.android.application", version.ref = "agp" }
 jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
 kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
 google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

From ab176f6405cd389318eff24395537896f7961515 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 13 Sep 2024 16:15:47 +0100
Subject: [PATCH 17/63] chore: move location from firebase.json to
 dataconnect.yaml

---
 dataconnect/dataconnect/dataconnect.yaml | 1 +
 dataconnect/firebase.json                | 3 +--
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/dataconnect/dataconnect/dataconnect.yaml b/dataconnect/dataconnect/dataconnect.yaml
index 5d09e5a65..a74d3b653 100644
--- a/dataconnect/dataconnect/dataconnect.yaml
+++ b/dataconnect/dataconnect/dataconnect.yaml
@@ -1,5 +1,6 @@
 specVersion: "v1alpha"
 serviceId: "dataconnect"
+location: "us-central1"
 schema:
   source: "./schema"
   datasource:
diff --git a/dataconnect/firebase.json b/dataconnect/firebase.json
index fe0eb6bfc..73f599717 100644
--- a/dataconnect/firebase.json
+++ b/dataconnect/firebase.json
@@ -1,6 +1,5 @@
 {
   "dataconnect": {
-    "source": "dataconnect",
-    "location": "us-central1"
+    "source": "dataconnect"
   }
 }

From de4b232f31452f766dfe57aa829a584425cdb84a Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 13 Sep 2024 17:58:04 +0100
Subject: [PATCH 18/63] feat: create an auth screen

---
 .../dataconnect/feature/profile/Navigation.kt |   2 +-
 .../feature/profile/ProfileScreen.kt          | 195 ++++++++++++++++++
 .../feature/profile/ProfileUIState.kt         |  14 ++
 .../feature/profile/ProfileViewModel.kt       |  74 +++++++
 4 files changed, 284 insertions(+), 1 deletion(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
index 156ba105b..f6ce7a7f8 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
@@ -14,7 +14,7 @@ fun NavGraphBuilder.profileScreen(
 
 ) {
     composable(route = PROFILE_ROUTE) {
-        // TODO: Call composable
+        ProfileScreen()
     }
 }
 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
new file mode 100644
index 000000000..81c2c6557
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -0,0 +1,195 @@
+package com.google.firebase.example.dataconnect.feature.profile
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.google.firebase.dataconnect.movies.GetUserByIdQuery
+import com.google.firebase.dataconnect.movies.ListUsersQuery
+
+@Composable
+fun ProfileScreen(
+    profileViewModel: ProfileViewModel = viewModel()
+) {
+    val uiState by profileViewModel.uiState.collectAsState()
+    when (uiState) {
+        is ProfileUIState.Error -> {
+            Text((uiState as ProfileUIState.Error).errorMessage)
+        }
+
+        is ProfileUIState.SignUpState -> {
+            AuthScreen(
+                onSignUp = { email, password, displayName ->
+                    profileViewModel.signUp(email, password, displayName)
+                },
+                onSignIn = {email, password ->
+                    profileViewModel.signIn(email, password)
+                }
+            )
+        }
+
+        is ProfileUIState.ProfileState -> {
+            val userName = (uiState as ProfileUIState.ProfileState).username
+            ProfileScreen(
+                userName ?: "User",
+                emptyList(),
+                emptyList(),
+                emptyList()
+            )
+        }
+
+        ProfileUIState.Loading -> {
+            Box(
+                contentAlignment = Alignment.Center,
+                modifier = Modifier.fillMaxSize()
+            ) {
+                CircularProgressIndicator()
+            }
+        }
+    }
+}
+
+@Composable
+fun ProfileScreen(
+    name: String,
+    reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>,
+    favoriteMovies: List<ListUsersQuery.Data.UsersItem.FavoriteMoviesOnUserItem.Movie>,
+    favoriteActors: List<ListUsersQuery.Data.UsersItem.FavoriteActorsOnUserItem.Actor>
+) {
+    Column(modifier = Modifier.padding(16.dp)) {
+        Text(
+            text = "Welcome back, $name!",
+            style = MaterialTheme.typography.titleLarge
+        )
+        Spacer(modifier = Modifier.height(16.dp))
+
+        ProfileSection(title = "Reviews", content = { ReviewsList(reviews) })
+        Spacer(modifier = Modifier.height(16.dp))
+
+        ProfileSection(title = "Favorite Movies", content = { FavoriteMoviesList(favoriteMovies) })
+        Spacer(modifier = Modifier.height(16.dp))
+
+        ProfileSection(title = "Favorite Actors", content = { FavoriteActorsList(favoriteActors) })
+    }
+}
+
+@Composable
+fun ProfileSection(title: String, content: @Composable () -> Unit) {
+    Column {
+        Text(text = title, style = MaterialTheme.typography.titleMedium)
+        Spacer(modifier = Modifier.height(8.dp))
+        content()
+    }
+}
+
+@Composable
+fun ReviewsList(reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>) {
+    // Display the list of reviews
+}
+
+@Composable
+fun FavoriteMoviesList(movies: List<ListUsersQuery.Data.UsersItem.FavoriteMoviesOnUserItem.Movie>) {
+    // Display the list of favorite movies
+}
+
+@Composable
+fun FavoriteActorsList(actors: List<ListUsersQuery.Data.UsersItem.FavoriteActorsOnUserItem.Actor>) {
+    // Display the list of favorite actors
+}
+
+@Composable
+fun AuthScreen(
+    onSignUp: (email: String, password: String, displayName: String) -> Unit,
+    onSignIn: (email: String, password: String) -> Unit,
+) {
+    var isSignUp by remember { mutableStateOf(false) }
+    var email by remember { mutableStateOf("") }
+    var password by remember { mutableStateOf("") }
+    var displayName by remember { mutableStateOf("") }
+
+    Column(
+        modifier = Modifier.fillMaxSize(),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        OutlinedTextField(
+            value = email,
+            onValueChange = { email = it },
+            label = { Text("Email") }
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        OutlinedTextField(
+            value = password,
+            onValueChange = { password = it },
+            label = { Text("Password") },
+            visualTransformation = PasswordVisualTransformation()
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        if (isSignUp) {
+            OutlinedTextField(
+                value = displayName,
+                onValueChange = { displayName = it },
+                label = { Text("Name") }
+            )
+        }
+        Spacer(modifier = Modifier.height(16.dp))
+        Button(onClick = {
+            if (isSignUp) {
+                onSignUp(email, password, displayName)
+            } else {
+                onSignIn(email, password)
+            }
+        }) {
+            Text(
+                text= if (isSignUp) {
+                    "Sign up"
+                } else {
+                    "Sign in"
+                })
+        }
+//        Spacer(modifier = Modifier.height(8.dp))
+//        Button(onClick = { /* Handle Google Sign-in */ }) {
+//            Text("Sign in with Google")
+//        }
+
+        Spacer(modifier = Modifier.height(16.dp))
+        Text(
+            text = if (isSignUp) {
+                "Already have an account?"
+            } else {
+                "Don't have an account?"
+            }
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        Button(onClick = {
+            isSignUp = !isSignUp
+        }) {
+            Text(
+                text = if (isSignUp) {
+                    "Sign in"
+                } else {
+                    "Sign up"
+                }
+            )
+        }
+    }
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
new file mode 100644
index 000000000..a9c5970c0
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
@@ -0,0 +1,14 @@
+package com.google.firebase.example.dataconnect.feature.profile
+
+sealed class ProfileUIState {
+    data object Loading: ProfileUIState()
+
+    data class Error(val errorMessage: String): ProfileUIState()
+
+    data object SignUpState: ProfileUIState()
+
+    data class ProfileState(
+        val username: String?,
+        // TODO: add reviews and favorites🔄
+    ) : ProfileUIState()
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
new file mode 100644
index 000000000..977d00366
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -0,0 +1,74 @@
+package com.google.firebase.example.dataconnect.feature.profile
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.google.firebase.Firebase
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.FirebaseAuth.AuthStateListener
+import com.google.firebase.auth.UserProfileChangeRequest
+import com.google.firebase.auth.auth
+import com.google.firebase.auth.userProfileChangeRequest
+import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailUIState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.tasks.await
+
+class ProfileViewModel(
+    private val auth: FirebaseAuth = Firebase.auth
+) : ViewModel() {
+    private val _uiState = MutableStateFlow<ProfileUIState>(ProfileUIState.Loading)
+    val uiState: StateFlow<ProfileUIState>
+        get() = _uiState
+
+    private val authStateListener: AuthStateListener
+
+    init {
+        authStateListener = object : AuthStateListener {
+            override fun onAuthStateChanged(auth: FirebaseAuth) {
+                val currentUser = auth.currentUser
+                if (currentUser != null) {
+                    _uiState.value = ProfileUIState.ProfileState(currentUser.displayName)
+                } else {
+                    _uiState.value = ProfileUIState.SignUpState
+                }
+            }
+        }
+        auth.addAuthStateListener(authStateListener)
+    }
+
+    fun signUp(
+        email: String,
+        password: String,
+        displayName: String
+    ) {
+        viewModelScope.launch {
+            try {
+                val signInResult = auth.createUserWithEmailAndPassword(email, password).await()
+                signInResult.user?.updateProfile(
+                    UserProfileChangeRequest.Builder()
+                        .setDisplayName(displayName)
+                        .build()
+                )?.await()
+            } catch (e: Exception) {
+                _uiState.value = ProfileUIState.Error(e.message ?: "")
+            }
+        }
+    }
+
+    fun signIn(email: String, password: String) {
+        viewModelScope.launch {
+            try {
+                auth.signInWithEmailAndPassword(email, password).await()
+            } catch (e: Exception) {
+                _uiState.value = ProfileUIState.Error(e.message ?: "")
+            }
+        }
+    }
+
+    override fun onCleared() {
+        super.onCleared()
+        auth.removeAuthStateListener(authStateListener)
+    }
+}
\ No newline at end of file

From d6de3ccb5a4eb7e519f9325b209a5245942dd871 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Tue, 17 Sep 2024 19:17:19 +0100
Subject: [PATCH 19/63] create user profile favorites list and sign out button

---
 .../dataconnect/data/MovieRepository.kt       |  5 +-
 .../dataconnect/data/UserRepository.kt        | 19 ++++
 .../feature/moviedetail/MovieDetailScreen.kt  | 39 +++++++--
 .../moviedetail/MovieDetailViewModel.kt       | 10 +++
 .../feature/profile/ProfileScreen.kt          | 86 +++++++++++++++----
 .../feature/profile/ProfileUIState.kt         |  7 +-
 .../feature/profile/ProfileViewModel.kt       | 30 ++++++-
 .../dataconnect/ui/components/MovieTile.kt    | 48 +++++++++++
 8 files changed, 220 insertions(+), 24 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
index 00dad663c..966ca7c4c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
@@ -9,7 +9,7 @@ class MovieRepository(
     private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) {
 
-    // Repositories
+    // Queries
     suspend fun listMovies(): List<Movie> {
         return moviesConnector.listMovies.execute().data.movies.map { it.toMovie() }
     }
@@ -35,4 +35,7 @@ class MovieRepository(
     }
 
     // Mutations
+    suspend fun addMovieToFavorites(movieID: String) {
+        moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieID))
+    }
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt
new file mode 100644
index 000000000..528c7a265
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt
@@ -0,0 +1,19 @@
+package com.google.firebase.example.dataconnect.data
+
+import com.google.firebase.dataconnect.movies.GetUserByIdQuery
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+
+class UserRepository(
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+) {
+
+    suspend fun getUserById(userId: String): GetUserByIdQuery.Data.User? {
+        return moviesConnector.getUserById.execute(id = userId).data.user
+    }
+
+    suspend fun addUser(userName: String) {
+        moviesConnector.upsertUser.execute(username = userName)
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index f43463d2f..26ace9ce5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -8,8 +8,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.FavoriteBorder
 import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -32,11 +38,28 @@ fun MovieDetailScreen(
 ) {
     movieDetailViewModel.setMovieId(movieId)
     val uiState by movieDetailViewModel.uiState.collectAsState()
-    MovieDetailScreen(uiState)
+    // TODO: Create a movie favorited toggle
+    Scaffold(
+        floatingActionButton = {
+            FloatingActionButton(
+                onClick = {
+                    movieDetailViewModel.addToFavorite()
+                }
+            ) {
+                Icon(Icons.Filled.FavoriteBorder, "Favorite")
+            }
+        }
+    ) { padding ->
+        MovieDetailScreen(
+            modifier = Modifier.padding(padding),
+            uiState = uiState
+        )
+    }
 }
 
 @Composable
 fun MovieDetailScreen(
+    modifier: Modifier = Modifier,
     uiState: MovieDetailUIState
 ) {
     when (uiState) {
@@ -47,7 +70,7 @@ fun MovieDetailScreen(
         MovieDetailUIState.Loading -> {
             Box(
                 contentAlignment = Alignment.Center,
-                modifier = Modifier.fillMaxSize()
+                modifier = modifier.fillMaxSize()
             ) {
                 CircularProgressIndicator()
             }
@@ -55,19 +78,25 @@ fun MovieDetailScreen(
 
         is MovieDetailUIState.Success -> {
             val movie = uiState.movie
-            MovieInformation(movie)
+            MovieInformation(
+                modifier = modifier,
+                movie = movie
+            )
         }
     }
 }
 
 @Composable
-fun MovieInformation(movie: Movie?) {
+fun MovieInformation(
+    modifier: Modifier = Modifier,
+    movie: Movie?
+) {
     if (movie == null) {
         ErrorMessage(stringResource(R.string.error_movie_not_found))
     } else {
         val scrollState = rememberScrollState()
         Column(
-            modifier = Modifier
+            modifier = modifier
                 .padding(16.dp)
                 .verticalScroll(scrollState)
         ) {
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index 7697bce16..008ac7f32 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -27,4 +27,14 @@ class MovieDetailViewModel(
             }
         }
     }
+
+    fun addToFavorite() {
+        viewModelScope.launch {
+            try {
+                repository.addMovieToFavorites(movieId)
+            } catch (e: Exception) {
+                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 81c2c6557..cb492b484 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -7,11 +7,16 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -24,7 +29,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.dataconnect.movies.GetUserByIdQuery
-import com.google.firebase.dataconnect.movies.ListUsersQuery
+import com.google.firebase.example.dataconnect.ui.components.MovieTile
 
 @Composable
 fun ProfileScreen(
@@ -41,19 +46,23 @@ fun ProfileScreen(
                 onSignUp = { email, password, displayName ->
                     profileViewModel.signUp(email, password, displayName)
                 },
-                onSignIn = {email, password ->
+                onSignIn = { email, password ->
                     profileViewModel.signIn(email, password)
                 }
             )
         }
 
         is ProfileUIState.ProfileState -> {
-            val userName = (uiState as ProfileUIState.ProfileState).username
+            val uiState = uiState as ProfileUIState.ProfileState
             ProfileScreen(
-                userName ?: "User",
-                emptyList(),
-                emptyList(),
-                emptyList()
+                uiState.username ?: "User",
+                uiState.reviews,
+                uiState.watchedMovies,
+                uiState.favoriteMovies,
+                uiState.favoriteActors,
+                onSignOut = {
+                    profileViewModel.signOut()
+                }
             )
         }
 
@@ -72,19 +81,34 @@ fun ProfileScreen(
 fun ProfileScreen(
     name: String,
     reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>,
-    favoriteMovies: List<ListUsersQuery.Data.UsersItem.FavoriteMoviesOnUserItem.Movie>,
-    favoriteActors: List<ListUsersQuery.Data.UsersItem.FavoriteActorsOnUserItem.Actor>
+    watchedMovies: List<GetUserByIdQuery.Data.User.WatchedItem>,
+    favoriteMovies: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem>,
+    favoriteActors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem>,
+    onSignOut: () -> Unit
 ) {
-    Column(modifier = Modifier.padding(16.dp)) {
+    val scrollState = rememberScrollState()
+    Column(
+        modifier = Modifier
+            .padding(16.dp)
+            .verticalScroll(scrollState)
+    ) {
         Text(
             text = "Welcome back, $name!",
             style = MaterialTheme.typography.titleLarge
         )
+        TextButton(onClick = {
+            onSignOut()
+        }) {
+            Text("Sign out")
+        }
         Spacer(modifier = Modifier.height(16.dp))
 
         ProfileSection(title = "Reviews", content = { ReviewsList(reviews) })
         Spacer(modifier = Modifier.height(16.dp))
 
+        ProfileSection(title = "Watched Movies", content = { WatchedMoviesList(watchedMovies) })
+        Spacer(modifier = Modifier.height(16.dp))
+
         ProfileSection(title = "Favorite Movies", content = { FavoriteMoviesList(favoriteMovies) })
         Spacer(modifier = Modifier.height(16.dp))
 
@@ -107,12 +131,43 @@ fun ReviewsList(reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>) {
 }
 
 @Composable
-fun FavoriteMoviesList(movies: List<ListUsersQuery.Data.UsersItem.FavoriteMoviesOnUserItem.Movie>) {
-    // Display the list of favorite movies
+fun WatchedMoviesList(watchedItems: List<GetUserByIdQuery.Data.User.WatchedItem>) {
+    LazyRow {
+        items(watchedItems) { watchedItem ->
+            MovieTile(
+                modifier = Modifier.fillParentMaxWidth(0.4f),
+                movieId = watchedItem.movie.id.toString(),
+                movieImageUrl = watchedItem.movie.imageUrl,
+                movieTitle = watchedItem.movie.title,
+                movieRating = watchedItem.movie.rating ?: 0.0,
+                onMovieClicked = {
+                    // TODO
+                }
+            )
+        }
+    }
 }
 
 @Composable
-fun FavoriteActorsList(actors: List<ListUsersQuery.Data.UsersItem.FavoriteActorsOnUserItem.Actor>) {
+fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem>) {
+    LazyRow {
+        items(favoriteItems) { favoriteItem ->
+            MovieTile(
+                modifier = Modifier.fillParentMaxWidth(0.4f),
+                movieId = favoriteItem.movie.id.toString(),
+                movieImageUrl = favoriteItem.movie.imageUrl,
+                movieTitle = favoriteItem.movie.title,
+                movieRating = favoriteItem.movie.rating ?: 0.0,
+                onMovieClicked = {
+                    // TODO
+                }
+            )
+        }
+    }
+}
+
+@Composable
+fun FavoriteActorsList(actors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem>) {
     // Display the list of favorite actors
 }
 
@@ -160,11 +215,12 @@ fun AuthScreen(
             }
         }) {
             Text(
-                text= if (isSignUp) {
+                text = if (isSignUp) {
                     "Sign up"
                 } else {
                     "Sign in"
-                })
+                }
+            )
         }
 //        Spacer(modifier = Modifier.height(8.dp))
 //        Button(onClick = { /* Handle Google Sign-in */ }) {
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
index a9c5970c0..df60528fb 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
@@ -1,5 +1,7 @@
 package com.google.firebase.example.dataconnect.feature.profile
 
+import com.google.firebase.dataconnect.movies.GetUserByIdQuery
+
 sealed class ProfileUIState {
     data object Loading: ProfileUIState()
 
@@ -9,6 +11,9 @@ sealed class ProfileUIState {
 
     data class ProfileState(
         val username: String?,
-        // TODO: add reviews and favorites🔄
+        val reviews: List<GetUserByIdQuery.Data.User.ReviewsItem> = emptyList(),
+        val watchedMovies: List<GetUserByIdQuery.Data.User.WatchedItem> = emptyList(),
+        val favoriteMovies: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem> = emptyList(),
+        val favoriteActors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem> = emptyList()
     ) : ProfileUIState()
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index 977d00366..aa31fc7ec 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -1,5 +1,6 @@
 package com.google.firebase.example.dataconnect.feature.profile
 
+import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewmodel.compose.viewModel
@@ -9,6 +10,7 @@ import com.google.firebase.auth.FirebaseAuth.AuthStateListener
 import com.google.firebase.auth.UserProfileChangeRequest
 import com.google.firebase.auth.auth
 import com.google.firebase.auth.userProfileChangeRequest
+import com.google.firebase.example.dataconnect.data.UserRepository
 import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailUIState
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -16,7 +18,8 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.tasks.await
 
 class ProfileViewModel(
-    private val auth: FirebaseAuth = Firebase.auth
+    private val auth: FirebaseAuth = Firebase.auth,
+    private val repository: UserRepository = UserRepository()
 ) : ViewModel() {
     private val _uiState = MutableStateFlow<ProfileUIState>(ProfileUIState.Loading)
     val uiState: StateFlow<ProfileUIState>
@@ -29,7 +32,7 @@ class ProfileViewModel(
             override fun onAuthStateChanged(auth: FirebaseAuth) {
                 val currentUser = auth.currentUser
                 if (currentUser != null) {
-                    _uiState.value = ProfileUIState.ProfileState(currentUser.displayName)
+                    displayUser(currentUser.uid)
                 } else {
                     _uiState.value = ProfileUIState.SignUpState
                 }
@@ -51,8 +54,10 @@ class ProfileViewModel(
                         .setDisplayName(displayName)
                         .build()
                 )?.await()
+                repository.addUser(displayName)
             } catch (e: Exception) {
                 _uiState.value = ProfileUIState.Error(e.message ?: "")
+                e.printStackTrace()
             }
         }
     }
@@ -67,6 +72,27 @@ class ProfileViewModel(
         }
     }
 
+    fun signOut() {
+        auth.signOut()
+    }
+
+    private fun displayUser(
+        userId: String
+    ) {
+        viewModelScope.launch {
+            try {
+                val user = repository.getUserById(userId)
+                _uiState.value = ProfileUIState.ProfileState(
+                    user?.username,
+                    favoriteMovies = user?.favoriteMovies ?: emptyList()
+                )
+                Log.d("DisplayUser", "$user")
+            } catch (e: Exception) {
+                _uiState.value = ProfileUIState.Error(e.message ?: "")
+            }
+        }
+    }
+
     override fun onCleared() {
         super.onCleared()
         auth.removeAuthStateListener(authStateListener)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
new file mode 100644
index 000000000..02167f5b0
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
@@ -0,0 +1,48 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+
+@Composable
+fun MovieTile(
+    modifier: Modifier = Modifier,
+    movieId: String,
+    movieImageUrl: String,
+    movieTitle: String,
+    movieRating: Double,
+    onMovieClicked: (movieId: String) -> Unit
+) {
+    Card(
+        modifier = modifier
+            .padding(4.dp)
+            .clickable {
+                onMovieClicked(movieId)
+            },
+    ) {
+        AsyncImage(
+            model = movieImageUrl,
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            modifier = Modifier.fillMaxWidth()
+        )
+        Text(
+            text = movieTitle,
+            style = MaterialTheme.typography.titleMedium,
+            modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp)
+        )
+        Text(
+            text = "Rating: $movieRating",
+            modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
+            style = MaterialTheme.typography.bodySmall
+        )
+    }
+}
\ No newline at end of file

From acc78fc96595be1139f3baee03f637fe648275f0 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 00:27:19 +0100
Subject: [PATCH 20/63] ellipsize text in Movie Tile

---
 .../feature/profile/ProfileScreen.kt          | 22 ++++++++++++-------
 .../dataconnect/ui/components/MovieTile.kt    | 16 ++++++++++++--
 2 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index cb492b484..dc14399ac 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -89,16 +89,20 @@ fun ProfileScreen(
     val scrollState = rememberScrollState()
     Column(
         modifier = Modifier
-            .padding(16.dp)
+            .padding(vertical = 16.dp)
             .verticalScroll(scrollState)
     ) {
         Text(
             text = "Welcome back, $name!",
-            style = MaterialTheme.typography.titleLarge
+            style = MaterialTheme.typography.displaySmall,
+            modifier = Modifier.padding(horizontal = 16.dp)
         )
-        TextButton(onClick = {
-            onSignOut()
-        }) {
+        TextButton(
+            onClick = {
+                onSignOut()
+            },
+            modifier = Modifier.padding(horizontal = 16.dp)
+        ) {
             Text("Sign out")
         }
         Spacer(modifier = Modifier.height(16.dp))
@@ -119,7 +123,11 @@ fun ProfileScreen(
 @Composable
 fun ProfileSection(title: String, content: @Composable () -> Unit) {
     Column {
-        Text(text = title, style = MaterialTheme.typography.titleMedium)
+        Text(
+            text = title,
+            style = MaterialTheme.typography.headlineMedium,
+            modifier = Modifier.padding(horizontal = 16.dp)
+        )
         Spacer(modifier = Modifier.height(8.dp))
         content()
     }
@@ -135,7 +143,6 @@ fun WatchedMoviesList(watchedItems: List<GetUserByIdQuery.Data.User.WatchedItem>
     LazyRow {
         items(watchedItems) { watchedItem ->
             MovieTile(
-                modifier = Modifier.fillParentMaxWidth(0.4f),
                 movieId = watchedItem.movie.id.toString(),
                 movieImageUrl = watchedItem.movie.imageUrl,
                 movieTitle = watchedItem.movie.title,
@@ -153,7 +160,6 @@ fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMo
     LazyRow {
         items(favoriteItems) { favoriteItem ->
             MovieTile(
-                modifier = Modifier.fillParentMaxWidth(0.4f),
                 movieId = favoriteItem.movie.id.toString(),
                 movieImageUrl = favoriteItem.movie.imageUrl,
                 movieTitle = favoriteItem.movie.title,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
index 02167f5b0..503df2bf7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
@@ -1,17 +1,26 @@
 package com.google.firebase.example.dataconnect.ui.components
 
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.Card
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 
+val MAX_MOVIE_CARD_WIDTH = 120.dp
+
 @Composable
 fun MovieTile(
     modifier: Modifier = Modifier,
@@ -23,7 +32,8 @@ fun MovieTile(
 ) {
     Card(
         modifier = modifier
-            .padding(4.dp)
+            .padding(vertical = 16.dp, horizontal = 4.dp)
+            .sizeIn(maxWidth = MAX_MOVIE_CARD_WIDTH)
             .clickable {
                 onMovieClicked(movieId)
             },
@@ -37,7 +47,9 @@ fun MovieTile(
         Text(
             text = movieTitle,
             style = MaterialTheme.typography.titleMedium,
-            modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp)
+            modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp),
+            maxLines = 1,
+            overflow = TextOverflow.Ellipsis
         )
         Text(
             text = "Rating: $movieRating",

From f37f21ea60713836ab07ff7d146086281147ba6b Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 00:40:53 +0100
Subject: [PATCH 21/63] refactor: reuse MovieTile UI component

---
 .../feature/genredetail/GenreDetailScreen.kt  | 41 ++++++++----
 .../feature/movies/MoviesScreen.kt            | 66 ++++++++-----------
 .../dataconnect/ui/components/MovieTile.kt    |  5 +-
 3 files changed, 61 insertions(+), 51 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 40808c519..9f836e020 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.CircularProgressIndicator
@@ -20,7 +22,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.feature.movies.HorizontalMovieList
+import com.google.firebase.example.dataconnect.ui.components.MovieTile
 
 @Composable
 fun GenreDetailScreen(
@@ -45,9 +47,11 @@ fun GenreDetailScreen(
                 CircularProgressIndicator()
             }
         }
+
         is GenreDetailUIState.Error -> {
             Text(uiState.errorMessage)
         }
+
         is GenreDetailUIState.Success -> {
             val scrollState = rememberScrollState()
             Column(
@@ -64,23 +68,38 @@ fun GenreDetailScreen(
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(
-                    uiState.moviesByGenre.mostPopular,
-                    onMovieClicked = {
-                        // TODO(thatfiredev)
+                LazyRow {
+                    items(uiState.moviesByGenre.mostPopular) { movie ->
+                        MovieTile(
+                            movieId = movie.id.toString(),
+                            movieTitle = movie.title,
+                            movieImageUrl = movie.imageUrl,
+                            movieRating = movie.rating ?: 0.0,
+                            onMovieClicked = {
+                                // TODO
+                            }
+                        )
                     }
-                )
+                }
+
                 Text(
                     text = stringResource(R.string.title_most_recent),
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(
-                    uiState.moviesByGenre.mostRecent,
-                    onMovieClicked = {
-                        // TODO(thatfiredev)
+                LazyRow {
+                    items(uiState.moviesByGenre.mostRecent) { movie ->
+                        MovieTile(
+                            movieId = movie.id.toString(),
+                            movieTitle = movie.title,
+                            movieImageUrl = movie.imageUrl,
+                            movieRating = movie.rating ?: 0.0,
+                            onMovieClicked = {
+                                // TODO(thatfiredev)
+                            }
+                        )
                     }
-                )
+                }
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index 1fddeb992..d2484cde7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -26,6 +26,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.data.Movie
+import com.google.firebase.example.dataconnect.ui.components.MovieTile
 
 @Composable
 fun MoviesScreen(
@@ -64,49 +65,38 @@ fun MoviesScreen(
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(bottom = 16.dp)
                 )
-                HorizontalMovieList(uiState.top10movies, onMovieClicked)
+                LazyRow {
+                    items(uiState.top10movies) { movie ->
+                        MovieTile(
+                            movieId = movie.id.toString(),
+                            movieTitle = movie.title,
+                            movieImageUrl = movie.imageUrl,
+                            movieRating = movie.rating ?: 0.0,
+                            onMovieClicked = {
+                                onMovieClicked(movie.id.toString())
+                            }
+                        )
+                    }
+                }
+
                 Text(
                     text = stringResource(R.string.title_latest_movies),
                     style = MaterialTheme.typography.headlineMedium,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
-                HorizontalMovieList(uiState.latestMovies, onMovieClicked)
-            }
-        }
-    }
-}
-
-@Composable
-fun HorizontalMovieList(
-    movies: List<Movie>,
-    onMovieClicked: (movie: String) -> Unit
-) {
-    LazyRow {
-        items(movies) { movie ->
-            Card(
-                modifier = Modifier
-                    .padding(4.dp)
-                    .fillParentMaxWidth(0.4f)
-                    .clickable {
-                        onMovieClicked(movie.id.toString())
-                    },
-            ) {
-                AsyncImage(
-                    model = movie.imageUrl,
-                    contentDescription = null,
-                    contentScale = ContentScale.Crop,
-                    modifier = Modifier.fillMaxWidth()
-                )
-                Text(
-                    text = movie.title,
-                    style = MaterialTheme.typography.titleMedium,
-                    modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp)
-                )
-                Text(
-                    text = "Rating: ${movie.rating}",
-                    modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
-                    style = MaterialTheme.typography.bodySmall
-                )
+                LazyRow {
+                    items(uiState.latestMovies) { movie ->
+                        MovieTile(
+                            movieId = movie.id.toString(),
+                            movieTitle = movie.title,
+                            movieImageUrl = movie.imageUrl,
+                            movieRating = movie.rating ?: 0.0,
+                            onMovieClicked = {
+                                onMovieClicked(movie.id.toString())
+                            }
+                        )
+                    }
+                }
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
index 503df2bf7..70f7ecba7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
@@ -4,6 +4,7 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -19,7 +20,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 
-val MAX_MOVIE_CARD_WIDTH = 120.dp
+val MAX_MOVIE_CARD_WIDTH = 150.dp
 
 @Composable
 fun MovieTile(
@@ -42,7 +43,7 @@ fun MovieTile(
             model = movieImageUrl,
             contentDescription = null,
             contentScale = ContentScale.Crop,
-            modifier = Modifier.fillMaxWidth()
+            modifier = Modifier.aspectRatio(9f / 16f)
         )
         Text(
             text = movieTitle,

From afe7d6924f999899a115ea296d7bdf9bd1cd2d8b Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 00:43:08 +0100
Subject: [PATCH 22/63] delete UserRepository

---
 .../dataconnect/data/UserRepository.kt        | 19 -------------------
 .../feature/profile/ProfileViewModel.kt       | 10 ++++++----
 2 files changed, 6 insertions(+), 23 deletions(-)
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt
deleted file mode 100644
index 528c7a265..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/UserRepository.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.google.firebase.example.dataconnect.data
-
-import com.google.firebase.dataconnect.movies.GetUserByIdQuery
-import com.google.firebase.dataconnect.movies.MoviesConnector
-import com.google.firebase.dataconnect.movies.execute
-import com.google.firebase.dataconnect.movies.instance
-
-class UserRepository(
-    private val moviesConnector: MoviesConnector = MoviesConnector.instance
-) {
-
-    suspend fun getUserById(userId: String): GetUserByIdQuery.Data.User? {
-        return moviesConnector.getUserById.execute(id = userId).data.user
-    }
-
-    suspend fun addUser(userName: String) {
-        moviesConnector.upsertUser.execute(username = userName)
-    }
-}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index aa31fc7ec..e24206ecd 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -10,7 +10,9 @@ import com.google.firebase.auth.FirebaseAuth.AuthStateListener
 import com.google.firebase.auth.UserProfileChangeRequest
 import com.google.firebase.auth.auth
 import com.google.firebase.auth.userProfileChangeRequest
-import com.google.firebase.example.dataconnect.data.UserRepository
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
 import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailUIState
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -19,7 +21,7 @@ import kotlinx.coroutines.tasks.await
 
 class ProfileViewModel(
     private val auth: FirebaseAuth = Firebase.auth,
-    private val repository: UserRepository = UserRepository()
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) : ViewModel() {
     private val _uiState = MutableStateFlow<ProfileUIState>(ProfileUIState.Loading)
     val uiState: StateFlow<ProfileUIState>
@@ -54,7 +56,7 @@ class ProfileViewModel(
                         .setDisplayName(displayName)
                         .build()
                 )?.await()
-                repository.addUser(displayName)
+                moviesConnector.upsertUser.execute(username = displayName)
             } catch (e: Exception) {
                 _uiState.value = ProfileUIState.Error(e.message ?: "")
                 e.printStackTrace()
@@ -81,7 +83,7 @@ class ProfileViewModel(
     ) {
         viewModelScope.launch {
             try {
-                val user = repository.getUserById(userId)
+                val user = moviesConnector.getUserById.execute(id = userId).data.user
                 _uiState.value = ProfileUIState.ProfileState(
                     user?.username,
                     favoriteMovies = user?.favoriteMovies ?: emptyList()

From e3dd016382c737d6de5c3221478e8e3b0debc478 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 00:48:36 +0100
Subject: [PATCH 23/63] refactor: delete MovieRepository

---
 .../dataconnect/data/MovieRepository.kt       | 41 -------------------
 .../genredetail/GenreDetailViewModel.kt       | 13 ++++--
 .../moviedetail/MovieDetailViewModel.kt       | 14 +++++--
 .../feature/movies/MoviesViewModel.kt         | 11 +++--
 4 files changed, 27 insertions(+), 52 deletions(-)
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
deleted file mode 100644
index 966ca7c4c..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MovieRepository.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.google.firebase.example.dataconnect.data
-
-import com.google.firebase.dataconnect.movies.MoviesConnector
-import com.google.firebase.dataconnect.movies.execute
-import com.google.firebase.dataconnect.movies.instance
-import java.util.UUID
-
-class MovieRepository(
-    private val moviesConnector: MoviesConnector = MoviesConnector.instance
-) {
-
-    // Queries
-    suspend fun listMovies(): List<Movie> {
-        return moviesConnector.listMovies.execute().data.movies.map { it.toMovie() }
-    }
-
-    suspend fun getTop10Movies(): List<Movie> {
-        return moviesConnector.moviesTop10.execute().data.movies.map { it.toMovie() }
-    }
-
-    suspend fun getRecentlyReleasedMovies(): List<Movie> {
-        return moviesConnector.moviesRecentlyReleased.execute().data.movies.map { it.toMovie() }
-    }
-
-    suspend fun getMoviesByGenre(genre: String): MoviesByGenre {
-        val data = moviesConnector.listMoviesByGenre.execute(genre).data
-        val mostPopular = data.mostPopular.map { it.toMovie() }
-        val mostRecent = data.mostRecent.map { it.toMovie() }
-        return MoviesByGenre(mostPopular, mostRecent)
-    }
-
-    suspend fun getMovieByID(movieID: String): Movie? {
-        val id = UUID.fromString(movieID)
-        return moviesConnector.getMovieById.execute(id).data.movie?.toMovie()
-    }
-
-    // Mutations
-    suspend fun addMovieToFavorites(movieID: String) {
-        moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieID))
-    }
-}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
index 912e11540..80a053b7a 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
@@ -2,13 +2,17 @@ package com.google.firebase.example.dataconnect.feature.genredetail
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.google.firebase.example.dataconnect.data.MovieRepository
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+import com.google.firebase.example.dataconnect.data.MoviesByGenre
+import com.google.firebase.example.dataconnect.data.toMovie
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class GenreDetailViewModel(
-    private val dataRepository: MovieRepository = MovieRepository()
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) : ViewModel() {
     private var genre = ""
 
@@ -21,7 +25,10 @@ class GenreDetailViewModel(
         this.genre = genre
         viewModelScope.launch {
             try {
-                val movies = dataRepository.getMoviesByGenre(genre.lowercase())
+                val data = moviesConnector.listMoviesByGenre.execute(genre.lowercase()).data
+                val mostPopular = data.mostPopular.map { it.toMovie() }
+                val mostRecent = data.mostRecent.map { it.toMovie() }
+                val movies = MoviesByGenre(mostPopular, mostRecent)
                 _uiState.value = GenreDetailUIState.Success(
                     genreName = genre,
                     moviesByGenre = movies
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index 008ac7f32..afeb672f0 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -2,13 +2,17 @@ package com.google.firebase.example.dataconnect.feature.moviedetail
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.google.firebase.example.dataconnect.data.MovieRepository
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+import com.google.firebase.example.dataconnect.data.toMovie
+import java.util.UUID
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class MovieDetailViewModel(
-    private val repository: MovieRepository = MovieRepository()
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) : ViewModel() {
     private var movieId: String = ""
 
@@ -20,7 +24,9 @@ class MovieDetailViewModel(
         movieId = id
         viewModelScope.launch {
             try {
-                val movie = repository.getMovieByID(movieId)
+                val movie =  moviesConnector.getMovieById.execute(
+                    id = UUID.fromString(movieId)
+                ).data.movie?.toMovie()
                 _uiState.value = MovieDetailUIState.Success(movie)
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
@@ -31,7 +37,7 @@ class MovieDetailViewModel(
     fun addToFavorite() {
         viewModelScope.launch {
             try {
-                repository.addMovieToFavorites(movieId)
+                moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieId))
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
index 09fe510bd..63f0cc3be 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
@@ -2,13 +2,16 @@ package com.google.firebase.example.dataconnect.feature.movies
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.google.firebase.example.dataconnect.data.MovieRepository
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+import com.google.firebase.example.dataconnect.data.toMovie
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class MoviesViewModel(
-    private val dataRepository: MovieRepository = MovieRepository()
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) : ViewModel() {
 
     private val _uiState = MutableStateFlow<MoviesUIState>(MoviesUIState.Loading)
@@ -18,8 +21,8 @@ class MoviesViewModel(
     init {
         viewModelScope.launch {
             try {
-                val top10Movies = dataRepository.getTop10Movies()
-                val latestMovies = dataRepository.getRecentlyReleasedMovies()
+                val top10Movies = moviesConnector.moviesTop10.execute().data.movies.map { it.toMovie() }
+                val latestMovies = moviesConnector.moviesRecentlyReleased.execute().data.movies.map { it.toMovie() }
 
                 _uiState.value = MoviesUIState.Success(top10Movies, latestMovies)
             } catch (e: Exception) {

From eb76c9290f2cced7a3869fa30a8ac6b15c27cdee Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 00:57:22 +0100
Subject: [PATCH 24/63] refactor: delete the data package

---
 .../dataconnect/data/DataConnectMappers.kt    | 73 -------------------
 .../example/dataconnect/data/Movie.kt         | 14 ----
 .../example/dataconnect/data/MoviesByGenre.kt |  6 --
 .../feature/genredetail/GenreDetailScreen.kt  |  4 +-
 .../feature/genredetail/GenreDetailUIState.kt |  6 +-
 .../genredetail/GenreDetailViewModel.kt       | 12 ++-
 .../feature/moviedetail/MovieDetailScreen.kt  |  5 +-
 .../feature/moviedetail/MovieDetailUIState.kt |  5 +-
 .../moviedetail/MovieDetailViewModel.kt       |  3 +-
 .../feature/movies/MoviesScreen.kt            |  1 -
 .../feature/movies/MoviesUIState.kt           |  9 ++-
 .../feature/movies/MoviesViewModel.kt         |  7 +-
 12 files changed, 24 insertions(+), 121 deletions(-)
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
deleted file mode 100644
index 8dc63c5e5..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/DataConnectMappers.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.google.firebase.example.dataconnect.data
-
-import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
-import com.google.firebase.dataconnect.movies.ListMoviesByGenreQuery
-import com.google.firebase.dataconnect.movies.ListMoviesQuery
-import com.google.firebase.dataconnect.movies.MoviesRecentlyReleasedQuery
-import com.google.firebase.dataconnect.movies.MoviesTop10Query
-
-fun ListMoviesQuery.Data.MoviesItem.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        genre = this.genre,
-        imageUrl = this.imageUrl,
-        releaseYear = this.releaseYear,
-        rating = this.rating,
-        tags = this.tags
-    )
-}
-
-fun MoviesTop10Query.Data.MoviesItem.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        imageUrl = this.imageUrl,
-        rating = this.rating,
-        genre = this.genre,
-        tags = this.tags
-    )
-}
-
-fun MoviesRecentlyReleasedQuery.Data.MoviesItem.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        imageUrl = this.imageUrl,
-        rating = this.rating,
-        genre = this.genre,
-        tags = this.tags
-    )
-}
-
-fun ListMoviesByGenreQuery.Data.MostRecentItem.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        imageUrl = this.imageUrl,
-        rating = this.rating,
-        tags = this.tags
-    )
-}
-
-fun ListMoviesByGenreQuery.Data.MostPopularItem.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        imageUrl = this.imageUrl,
-        rating = this.rating,
-        tags = this.tags
-    )
-}
-
-fun GetMovieByIdQuery.Data.Movie.toMovie(): Movie {
-    return Movie(
-        id = this.id,
-        title = this.title,
-        description = this.description,
-        imageUrl = this.imageUrl,
-        rating = this.rating,
-        genre = this.genre,
-        tags = this.tags
-    )
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
deleted file mode 100644
index bbd08c19b..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/Movie.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.google.firebase.example.dataconnect.data
-
-import java.util.UUID
-
-data class Movie(
-    val id: UUID,
-    val title: String,
-    val imageUrl: String,
-    val releaseYear: Int? = 1970,
-    val genre: String? = "",
-    val rating: Double? = 0.0,
-    val description: String? = null,
-    val tags: List<String?>? = emptyList()
-)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt
deleted file mode 100644
index e26e14a62..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/data/MoviesByGenre.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.google.firebase.example.dataconnect.data
-
-data class MoviesByGenre(
-    val mostPopular: List<Movie>,
-    val mostRecent: List<Movie>
-)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 9f836e020..22a4c8f4e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -69,7 +69,7 @@ fun GenreDetailScreen(
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
                 LazyRow {
-                    items(uiState.moviesByGenre.mostPopular) { movie ->
+                    items(uiState.mostPopular) { movie ->
                         MovieTile(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
@@ -88,7 +88,7 @@ fun GenreDetailScreen(
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
                 LazyRow {
-                    items(uiState.moviesByGenre.mostRecent) { movie ->
+                    items(uiState.mostRecent) { movie ->
                         MovieTile(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
index b4b99178e..fefb51f1d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
@@ -1,7 +1,6 @@
 package com.google.firebase.example.dataconnect.feature.genredetail
 
-import com.google.firebase.example.dataconnect.data.Movie
-import com.google.firebase.example.dataconnect.data.MoviesByGenre
+import com.google.firebase.dataconnect.movies.ListMoviesByGenreQuery
 
 sealed class GenreDetailUIState {
 
@@ -11,6 +10,7 @@ sealed class GenreDetailUIState {
 
     data class Success(
         val genreName: String,
-        val moviesByGenre: MoviesByGenre
+        val mostPopular: List<ListMoviesByGenreQuery.Data.MostPopularItem>,
+        val mostRecent: List<ListMoviesByGenreQuery.Data.MostRecentItem>
     ) : GenreDetailUIState()
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
index 80a053b7a..811250a78 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
@@ -5,8 +5,6 @@ import androidx.lifecycle.viewModelScope
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
-import com.google.firebase.example.dataconnect.data.MoviesByGenre
-import com.google.firebase.example.dataconnect.data.toMovie
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
@@ -26,16 +24,16 @@ class GenreDetailViewModel(
         viewModelScope.launch {
             try {
                 val data = moviesConnector.listMoviesByGenre.execute(genre.lowercase()).data
-                val mostPopular = data.mostPopular.map { it.toMovie() }
-                val mostRecent = data.mostRecent.map { it.toMovie() }
-                val movies = MoviesByGenre(mostPopular, mostRecent)
+                val mostPopular = data.mostPopular
+                val mostRecent = data.mostRecent
                 _uiState.value = GenreDetailUIState.Success(
                     genreName = genre,
-                    moviesByGenre = movies
+                    mostPopular = mostPopular,
+                    mostRecent = mostRecent
                 )
             } catch (e: Exception) {
                 _uiState.value = GenreDetailUIState.Error(e.message ?: "")
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 26ace9ce5..4b3c067d4 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.FavoriteBorder
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.FloatingActionButton
@@ -28,8 +27,8 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
+import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.data.Movie
 
 @Composable
 fun MovieDetailScreen(
@@ -89,7 +88,7 @@ fun MovieDetailScreen(
 @Composable
 fun MovieInformation(
     modifier: Modifier = Modifier,
-    movie: Movie?
+    movie: GetMovieByIdQuery.Data.Movie?
 ) {
     if (movie == null) {
         ErrorMessage(stringResource(R.string.error_movie_not_found))
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
index 685cb73e1..0f245e440 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
@@ -1,6 +1,7 @@
 package com.google.firebase.example.dataconnect.feature.moviedetail
 
-import com.google.firebase.example.dataconnect.data.Movie
+import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
+
 
 sealed class MovieDetailUIState {
     data object Loading: MovieDetailUIState()
@@ -9,6 +10,6 @@ sealed class MovieDetailUIState {
 
     data class Success(
         // Movie is null if it can't be found on the DB
-        val movie: Movie?
+        val movie: GetMovieByIdQuery.Data.Movie?
     ) : MovieDetailUIState()
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index afeb672f0..e36729496 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
-import com.google.firebase.example.dataconnect.data.toMovie
 import java.util.UUID
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -26,7 +25,7 @@ class MovieDetailViewModel(
             try {
                 val movie =  moviesConnector.getMovieById.execute(
                     id = UUID.fromString(movieId)
-                ).data.movie?.toMovie()
+                ).data.movie
                 _uiState.value = MovieDetailUIState.Success(movie)
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index d2484cde7..3fc75dce5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.data.Movie
 import com.google.firebase.example.dataconnect.ui.components.MovieTile
 
 @Composable
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
index 73aca9a07..92b8f70f3 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
@@ -1,6 +1,7 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
-import com.google.firebase.example.dataconnect.data.Movie
+import com.google.firebase.dataconnect.movies.MoviesRecentlyReleasedQuery
+import com.google.firebase.dataconnect.movies.MoviesTop10Query
 
 sealed class MoviesUIState {
 
@@ -9,7 +10,7 @@ sealed class MoviesUIState {
     data class Error(val errorMessage: String): MoviesUIState()
 
     data class Success(
-        val top10movies: List<Movie>,
-        val latestMovies: List<Movie>
+        val top10movies: List<MoviesTop10Query.Data.MoviesItem>,
+        val latestMovies: List<MoviesRecentlyReleasedQuery.Data.MoviesItem>
     ) : MoviesUIState()
-}
\ No newline at end of file
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
index 63f0cc3be..932dca76e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesViewModel.kt
@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
-import com.google.firebase.example.dataconnect.data.toMovie
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
@@ -21,8 +20,8 @@ class MoviesViewModel(
     init {
         viewModelScope.launch {
             try {
-                val top10Movies = moviesConnector.moviesTop10.execute().data.movies.map { it.toMovie() }
-                val latestMovies = moviesConnector.moviesRecentlyReleased.execute().data.movies.map { it.toMovie() }
+                val top10Movies = moviesConnector.moviesTop10.execute().data.movies
+                val latestMovies = moviesConnector.moviesRecentlyReleased.execute().data.movies
 
                 _uiState.value = MoviesUIState.Success(top10Movies, latestMovies)
             } catch (e: Exception) {
@@ -30,4 +29,4 @@ class MoviesViewModel(
             }
         }
     }
-}
\ No newline at end of file
+}

From 6a29c550d8373c869340d80fe546d476b38f8d65 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 01:21:27 +0100
Subject: [PATCH 25/63] feat: list actors in the movie details screen

---
 .../feature/moviedetail/MovieDetailScreen.kt  | 62 +++++++++++++++++--
 .../dataconnect/ui/components/ActorTile.kt    | 56 +++++++++++++++++
 2 files changed, 112 insertions(+), 6 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 4b3c067d4..ced985a6c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -3,9 +3,14 @@ package com.google.firebase.example.dataconnect.feature.moviedetail
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
@@ -29,6 +34,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.ui.components.ActorTile
 
 @Composable
 fun MovieDetailScreen(
@@ -77,10 +83,18 @@ fun MovieDetailScreen(
 
         is MovieDetailUIState.Success -> {
             val movie = uiState.movie
-            MovieInformation(
-                modifier = modifier,
-                movie = movie
-            )
+            val scrollState = rememberScrollState()
+            Column(
+                modifier = Modifier.verticalScroll(scrollState)
+            ) {
+                MovieInformation(
+                    modifier = modifier,
+                    movie = movie
+                )
+                MainActorsList(movie?.mainActors ?: emptyList())
+                SupportingActorsList(movie?.supportingActors ?: emptyList())
+            }
+
         }
     }
 }
@@ -93,11 +107,9 @@ fun MovieInformation(
     if (movie == null) {
         ErrorMessage(stringResource(R.string.error_movie_not_found))
     } else {
-        val scrollState = rememberScrollState()
         Column(
             modifier = modifier
                 .padding(16.dp)
-                .verticalScroll(scrollState)
         ) {
             Text(
                 text = movie.title,
@@ -147,6 +159,44 @@ fun MovieInformation(
     }
 }
 
+@Composable
+fun MainActorsList(
+    actors: List<GetMovieByIdQuery.Data.Movie.MainActorsItem?>
+) {
+    Text(
+        text = "Main Actors",
+        style = MaterialTheme.typography.headlineMedium,
+        modifier = Modifier.padding(horizontal = 16.dp)
+    )
+    Spacer(modifier = Modifier.height(8.dp))
+    LazyRow {
+        items(actors) { actor ->
+            actor?.let {
+                ActorTile(it.name, it.imageUrl)
+            }
+        }
+    }
+}
+
+@Composable
+fun SupportingActorsList(
+    actors: List<GetMovieByIdQuery.Data.Movie.SupportingActorsItem?>
+) {
+    Text(
+        text = "Supporting Actors",
+        style = MaterialTheme.typography.headlineMedium,
+        modifier = Modifier.padding(horizontal = 16.dp)
+    )
+    Spacer(modifier = Modifier.height(8.dp))
+    LazyRow {
+        items(actors) { actor ->
+            actor?.let {
+                ActorTile(it.name, it.imageUrl)
+            }
+        }
+    }
+}
+
 @Composable
 fun ErrorMessage(
     message: String
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
new file mode 100644
index 000000000..3a972ae69
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
@@ -0,0 +1,56 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import coil.compose.rememberAsyncImagePainter
+
+val ACTOR_CARD_SIZE = 80.dp
+
+@Composable
+fun ActorTile(
+    actorName: String,
+    actorImageUrl: String
+) {
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        modifier = Modifier.sizeIn(
+            maxWidth = ACTOR_CARD_SIZE,
+            maxHeight = ACTOR_CARD_SIZE + 32.dp
+        ).padding(4.dp)
+    ) {
+        AsyncImage(
+            model = actorImageUrl,
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            modifier = Modifier
+                .size(ACTOR_CARD_SIZE)
+                .clip(CircleShape)
+        )
+        Text(
+            text = actorName,
+            style = MaterialTheme.typography.bodyLarge,
+            maxLines = 1,
+            overflow = TextOverflow.Ellipsis
+        )
+    }
+}

From 0b9ef775efc07d0cf6054a16ac863f511ec766a3 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 02:56:45 +0100
Subject: [PATCH 26/63] refactor: movie review list to bottom of profile screen

---
 .../feature/profile/ProfileScreen.kt          | 35 ++++++++++--
 .../dataconnect/ui/components/ReviewCard.kt   | 57 +++++++++++++++++++
 2 files changed, 86 insertions(+), 6 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index dc14399ac..156450853 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
@@ -29,7 +30,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.dataconnect.movies.GetUserByIdQuery
+import com.google.firebase.example.dataconnect.ui.components.ActorTile
 import com.google.firebase.example.dataconnect.ui.components.MovieTile
+import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
 @Composable
 fun ProfileScreen(
@@ -107,9 +110,6 @@ fun ProfileScreen(
         }
         Spacer(modifier = Modifier.height(16.dp))
 
-        ProfileSection(title = "Reviews", content = { ReviewsList(reviews) })
-        Spacer(modifier = Modifier.height(16.dp))
-
         ProfileSection(title = "Watched Movies", content = { WatchedMoviesList(watchedMovies) })
         Spacer(modifier = Modifier.height(16.dp))
 
@@ -117,6 +117,9 @@ fun ProfileScreen(
         Spacer(modifier = Modifier.height(16.dp))
 
         ProfileSection(title = "Favorite Actors", content = { FavoriteActorsList(favoriteActors) })
+
+        ProfileSection(title = "Reviews", content = { ReviewsList(name, reviews) })
+        Spacer(modifier = Modifier.height(16.dp))
     }
 }
 
@@ -134,8 +137,21 @@ fun ProfileSection(title: String, content: @Composable () -> Unit) {
 }
 
 @Composable
-fun ReviewsList(reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>) {
-    // Display the list of reviews
+fun ReviewsList(
+    userName: String,
+    reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>
+) {
+    Column {
+        // TODO(thatfiredev): Handle cases where the list is too long to display
+        reviews.forEach { review ->
+            ReviewCard(
+                userName = userName,
+                date = review.reviewDate.toString(),
+                rating = review.rating?.toDouble() ?: 0.0,
+                text = review.reviewText ?: ""
+            )
+        }
+    }
 }
 
 @Composable
@@ -174,7 +190,14 @@ fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMo
 
 @Composable
 fun FavoriteActorsList(actors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem>) {
-    // Display the list of favorite actors
+    LazyRow {
+        items(actors) { favoriteActor ->
+            ActorTile(
+                actorName = favoriteActor.actor.name,
+                actorImageUrl = favoriteActor.actor.imageUrl
+            )
+        }
+    }
 }
 
 @Composable
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt
new file mode 100644
index 000000000..585b0fdd2
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt
@@ -0,0 +1,57 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import android.widget.Space
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.text
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ReviewCard(
+    userName: String,
+    date: String,
+    rating: Double,
+    text: String
+) {
+    Card(
+        modifier = Modifier
+            .background(color = MaterialTheme.colorScheme.onSecondaryContainer)
+            .fillMaxWidth()
+            .padding(8.dp)
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+        ) {
+            Text(
+                text = userName,
+                fontWeight = FontWeight.Bold,
+                style = MaterialTheme.typography.titleMedium
+            )
+            Row(
+                modifier = Modifier.padding(bottom = 8.dp)
+            ) {
+                Text(text = date)
+                Spacer(modifier = Modifier.width(8.dp))
+                Text(text = "Rating: ")
+                Text(text = "$rating")
+            }
+            Text(
+                text = text,
+                modifier = Modifier.fillMaxWidth()
+            )
+        }
+    }
+}

From 1af90a7db8fb2b8fc3cc3e5d0a0c979b81792786 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 02:56:57 +0100
Subject: [PATCH 27/63] feat: add reviews to movie detail screen

---
 .../feature/moviedetail/MovieDetailScreen.kt  | 115 +++++++++++++++---
 1 file changed, 99 insertions(+), 16 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index ced985a6c..ed62960d8 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -4,27 +4,36 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.FavoriteBorder
+import androidx.compose.material.icons.outlined.Check
+import androidx.compose.material.icons.outlined.Favorite
+import androidx.compose.material.icons.outlined.Star
+import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
@@ -35,6 +44,7 @@ import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.ui.components.ActorTile
+import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
 @Composable
 fun MovieDetailScreen(
@@ -45,15 +55,15 @@ fun MovieDetailScreen(
     val uiState by movieDetailViewModel.uiState.collectAsState()
     // TODO: Create a movie favorited toggle
     Scaffold(
-        floatingActionButton = {
-            FloatingActionButton(
-                onClick = {
-                    movieDetailViewModel.addToFavorite()
-                }
-            ) {
-                Icon(Icons.Filled.FavoriteBorder, "Favorite")
-            }
-        }
+//        floatingActionButton = {
+//            FloatingActionButton(
+//                onClick = {
+//                    movieDetailViewModel.addToFavorite()
+//                }
+//            ) {
+//                Icon(Icons.Filled.FavoriteBorder, "Favorite")
+//            }
+//        }
     ) { padding ->
         MovieDetailScreen(
             modifier = Modifier.padding(padding),
@@ -93,6 +103,12 @@ fun MovieDetailScreen(
                 )
                 MainActorsList(movie?.mainActors ?: emptyList())
                 SupportingActorsList(movie?.supportingActors ?: emptyList())
+                UserReviews(
+                    onReviewSubmitted = {
+
+                    },
+                    movie?.reviews ?: emptyList()
+                )
             }
 
         }
@@ -115,15 +131,20 @@ fun MovieInformation(
                 text = movie.title,
                 style = MaterialTheme.typography.headlineLarge
             )
-            Row {
+            Row(
+                verticalAlignment = Alignment.CenterVertically
+            ) {
                 Text(
                     text = movie.releaseYear.toString(),
-                    style = MaterialTheme.typography.labelMedium,
+                    style = MaterialTheme.typography.bodyLarge,
                     modifier = Modifier.padding(end = 4.dp)
                 )
+                Spacer(modifier = Modifier.width(8.dp))
+                Icon(Icons.Outlined.Star, "Favorite")
                 Text(
                     text = movie.rating?.toString() ?: "0.0",
-                    style = MaterialTheme.typography.labelMedium
+                    style = MaterialTheme.typography.bodyLarge,
+                    modifier = Modifier.padding(start = 2.dp)
                 )
             }
             Row {
@@ -132,10 +153,12 @@ fun MovieInformation(
                     contentDescription = null,
                     contentScale = ContentScale.Crop,
                     modifier = Modifier
+                        .width(150.dp)
+                        .aspectRatio(9f / 16f)
                         .padding(vertical = 8.dp)
                 )
                 Column(
-                    modifier = Modifier.padding(horizontal = 8.dp)
+                    modifier = Modifier.padding(horizontal = 16.dp)
                 ) {
                     Row {
                         movie.tags?.let { movieTags ->
@@ -144,7 +167,7 @@ fun MovieInformation(
                                     onClick = { },
                                     label = { Text(tag) },
                                     modifier = Modifier
-                                        .padding(horizontal = 4.dp, vertical = 8.dp)
+                                        .padding(horizontal = 4.dp)
                                 )
                             }
                         }
@@ -155,6 +178,22 @@ fun MovieInformation(
                     )
                 }
             }
+            Spacer(modifier = Modifier.height(8.dp))
+            Row {
+                OutlinedButton(onClick = {
+
+                }) {
+                    Icon(Icons.Outlined.Check, "Watched")
+                    Text("Mark as watched", modifier = Modifier.padding(start = 4.dp))
+                }
+                Spacer(modifier = Modifier.width(8.dp))
+                OutlinedButton(onClick = {
+
+                }) {
+                    Icon(Icons.Outlined.Favorite, "Favorite")
+                    Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
+                }
+            }
         }
     }
 }
@@ -197,6 +236,50 @@ fun SupportingActorsList(
     }
 }
 
+@Composable
+fun UserReviews(
+    onReviewSubmitted: (String) -> Unit,
+    reviews: List<GetMovieByIdQuery.Data.Movie.ReviewsItem>
+) {
+    var reviewText by remember { mutableStateOf("") }
+    Text(
+        text = "User Reviews",
+        style = MaterialTheme.typography.headlineMedium,
+        modifier = Modifier.padding(horizontal = 16.dp)
+    )
+    Spacer(modifier = Modifier.height(8.dp))
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(16.dp),
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        TextField(
+            value = reviewText,
+            onValueChange = { reviewText = it },
+            label = { Text("Write your review") },
+            modifier = Modifier.fillMaxWidth()
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        Button(onClick = { onReviewSubmitted(reviewText) }) {
+            Text("Submit Review")
+        }
+    }
+    Column {
+        // TODO(thatfiredev): Handle cases where the list is too long to display
+        reviews.forEach {
+            ReviewCard(
+                userName = it.user.username,
+                date = it.reviewDate.toString(),
+                rating = it.rating?.toDouble() ?: 0.0,
+                text = it.reviewText ?: ""
+            )
+        }
+    }
+}
+
 @Composable
 fun ErrorMessage(
     message: String

From d9293aee871e79c60afbce54aa971a38ffe038c9 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 03:22:56 +0100
Subject: [PATCH 28/63] refactor: dateformat for reviews

---
 .../feature/moviedetail/MovieDetailScreen.kt    | 10 +++++++---
 .../feature/profile/ProfileScreen.kt            |  2 +-
 .../dataconnect/ui/components/ReviewCard.kt     | 17 ++++++++++++++---
 3 files changed, 22 insertions(+), 7 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index ed62960d8..7130d572d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -208,7 +208,9 @@ fun MainActorsList(
         modifier = Modifier.padding(horizontal = 16.dp)
     )
     Spacer(modifier = Modifier.height(8.dp))
-    LazyRow {
+    LazyRow(
+        modifier = Modifier.padding(horizontal = 16.dp)
+    ) {
         items(actors) { actor ->
             actor?.let {
                 ActorTile(it.name, it.imageUrl)
@@ -227,7 +229,9 @@ fun SupportingActorsList(
         modifier = Modifier.padding(horizontal = 16.dp)
     )
     Spacer(modifier = Modifier.height(8.dp))
-    LazyRow {
+    LazyRow(
+        modifier = Modifier.padding(horizontal = 16.dp)
+    ) {
         items(actors) { actor ->
             actor?.let {
                 ActorTile(it.name, it.imageUrl)
@@ -272,7 +276,7 @@ fun UserReviews(
         reviews.forEach {
             ReviewCard(
                 userName = it.user.username,
-                date = it.reviewDate.toString(),
+                date = it.reviewDate,
                 rating = it.rating?.toDouble() ?: 0.0,
                 text = it.reviewText ?: ""
             )
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 156450853..e6059823e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -146,7 +146,7 @@ fun ReviewsList(
         reviews.forEach { review ->
             ReviewCard(
                 userName = userName,
-                date = review.reviewDate.toString(),
+                date = review.reviewDate,
                 rating = review.rating?.toDouble() ?: 0.0,
                 text = review.reviewText ?: ""
             )
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt
index 585b0fdd2..4df00e3c0 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ReviewCard.kt
@@ -17,22 +17,28 @@ import androidx.compose.ui.semantics.text
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.util.Date
+import java.util.Locale
+
 
 @Composable
 fun ReviewCard(
     userName: String,
-    date: String,
+    date: Date,
     rating: Double,
     text: String
 ) {
     Card(
         modifier = Modifier
-            .background(color = MaterialTheme.colorScheme.onSecondaryContainer)
             .fillMaxWidth()
             .padding(8.dp)
     ) {
         Column(
             modifier = Modifier
+                .background(color = MaterialTheme.colorScheme.secondaryContainer)
                 .padding(16.dp)
         ) {
             Text(
@@ -43,7 +49,12 @@ fun ReviewCard(
             Row(
                 modifier = Modifier.padding(bottom = 8.dp)
             ) {
-                Text(text = date)
+                Text(
+                    text = SimpleDateFormat(
+                        "dd MMM, yyyy",
+                        Locale.getDefault()
+                    ).format(date)
+                )
                 Spacer(modifier = Modifier.width(8.dp))
                 Text(text = "Rating: ")
                 Text(text = "$rating")

From a309c175ad02a8ac620b786ed87581cd76ee44a2 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 03:52:40 +0100
Subject: [PATCH 29/63] feat: mark movies as watched and/or favorite

---
 .../feature/moviedetail/MovieDetailScreen.kt  | 134 +++++++++++-------
 .../feature/moviedetail/MovieDetailUIState.kt |   5 +-
 .../moviedetail/MovieDetailViewModel.kt       |  66 ++++++++-
 .../feature/profile/ProfileViewModel.kt       |   5 +-
 4 files changed, 152 insertions(+), 58 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 7130d572d..6695c101a 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -10,17 +10,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.CheckCircle
+import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.outlined.Check
-import androidx.compose.material.icons.outlined.Favorite
+import androidx.compose.material.icons.outlined.FavoriteBorder
 import androidx.compose.material.icons.outlined.Star
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.FilledTonalButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
@@ -65,10 +67,51 @@ fun MovieDetailScreen(
 //            }
 //        }
     ) { padding ->
-        MovieDetailScreen(
-            modifier = Modifier.padding(padding),
-            uiState = uiState
-        )
+        when (uiState) {
+            is MovieDetailUIState.Error -> {
+                ErrorMessage((uiState as MovieDetailUIState.Error).errorMessage)
+            }
+
+            MovieDetailUIState.Loading -> {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier.fillMaxSize()
+                ) {
+                    CircularProgressIndicator()
+                }
+            }
+
+            is MovieDetailUIState.Success -> {
+                val ui = uiState as MovieDetailUIState.Success
+                val movie = ui.movie
+                val scrollState = rememberScrollState()
+                Column(
+                    modifier = Modifier.verticalScroll(scrollState)
+                ) {
+                    MovieInformation(
+                        modifier = Modifier.padding(padding),
+                        movie = movie,
+                        isMovieWatched = ui.isWatched,
+                        isMovieFavorite = ui.isFavorite,
+                        onFavoriteToggled = { newValue ->
+                            movieDetailViewModel.toggleFavorite(newValue)
+                        },
+                        onWatchToggled = { newValue ->
+                            movieDetailViewModel.toggleWatched(newValue)
+                        }
+                    )
+                    MainActorsList(movie?.mainActors ?: emptyList())
+                    SupportingActorsList(movie?.supportingActors ?: emptyList())
+                    UserReviews(
+                        onReviewSubmitted = {
+
+                        },
+                        movie?.reviews ?: emptyList()
+                    )
+                }
+
+            }
+        }
     }
 }
 
@@ -77,48 +120,17 @@ fun MovieDetailScreen(
     modifier: Modifier = Modifier,
     uiState: MovieDetailUIState
 ) {
-    when (uiState) {
-        is MovieDetailUIState.Error -> {
-            ErrorMessage(uiState.errorMessage)
-        }
 
-        MovieDetailUIState.Loading -> {
-            Box(
-                contentAlignment = Alignment.Center,
-                modifier = modifier.fillMaxSize()
-            ) {
-                CircularProgressIndicator()
-            }
-        }
-
-        is MovieDetailUIState.Success -> {
-            val movie = uiState.movie
-            val scrollState = rememberScrollState()
-            Column(
-                modifier = Modifier.verticalScroll(scrollState)
-            ) {
-                MovieInformation(
-                    modifier = modifier,
-                    movie = movie
-                )
-                MainActorsList(movie?.mainActors ?: emptyList())
-                SupportingActorsList(movie?.supportingActors ?: emptyList())
-                UserReviews(
-                    onReviewSubmitted = {
-
-                    },
-                    movie?.reviews ?: emptyList()
-                )
-            }
-
-        }
-    }
 }
 
 @Composable
 fun MovieInformation(
     modifier: Modifier = Modifier,
-    movie: GetMovieByIdQuery.Data.Movie?
+    movie: GetMovieByIdQuery.Data.Movie?,
+    isMovieWatched: Boolean,
+    isMovieFavorite: Boolean,
+    onWatchToggled: (newValue: Boolean) -> Unit,
+    onFavoriteToggled: (newValue: Boolean) -> Unit
 ) {
     if (movie == null) {
         ErrorMessage(stringResource(R.string.error_movie_not_found))
@@ -180,18 +192,36 @@ fun MovieInformation(
             }
             Spacer(modifier = Modifier.height(8.dp))
             Row {
-                OutlinedButton(onClick = {
-
-                }) {
-                    Icon(Icons.Outlined.Check, "Watched")
-                    Text("Mark as watched", modifier = Modifier.padding(start = 4.dp))
+                if (isMovieWatched) {
+                    FilledTonalButton(onClick = {
+                        onWatchToggled(false)
+                    }) {
+                        Icon(Icons.Filled.CheckCircle, "Watched")
+                        Text("Watched", modifier = Modifier.padding(start = 4.dp))
+                    }
+                } else {
+                    OutlinedButton(onClick = {
+                        onWatchToggled(true)
+                    }) {
+                        Icon(Icons.Outlined.Check, "Watched")
+                        Text("Mark as watched", modifier = Modifier.padding(start = 4.dp))
+                    }
                 }
                 Spacer(modifier = Modifier.width(8.dp))
-                OutlinedButton(onClick = {
-
-                }) {
-                    Icon(Icons.Outlined.Favorite, "Favorite")
-                    Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
+                if (isMovieFavorite) {
+                    FilledTonalButton(onClick = {
+                        onFavoriteToggled(false)
+                    }) {
+                        Icon(Icons.Filled.Favorite, "Favorite")
+                        Text("Favorite", modifier = Modifier.padding(start = 4.dp))
+                    }
+                } else {
+                    OutlinedButton(onClick = {
+                        onFavoriteToggled(true)
+                    }) {
+                        Icon(Icons.Outlined.FavoriteBorder, "Favorite")
+                        Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
+                    }
                 }
             }
         }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
index 0f245e440..d1e3bf071 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
@@ -10,6 +10,9 @@ sealed class MovieDetailUIState {
 
     data class Success(
         // Movie is null if it can't be found on the DB
-        val movie: GetMovieByIdQuery.Data.Movie?
+        val movie: GetMovieByIdQuery.Data.Movie?,
+        val isUserSignedIn: Boolean = false,
+        var isWatched: Boolean = false,
+        var isFavorite: Boolean = false
     ) : MovieDetailUIState()
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index e36729496..5ace553be 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -2,15 +2,20 @@ package com.google.firebase.example.dataconnect.feature.moviedetail
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.google.firebase.Firebase
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.auth
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
 import java.util.UUID
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 class MovieDetailViewModel(
+    private val firebaseAuth: FirebaseAuth = Firebase.auth,
     private val moviesConnector: MoviesConnector = MoviesConnector.instance
 ) : ViewModel() {
     private var movieId: String = ""
@@ -23,20 +28,73 @@ class MovieDetailViewModel(
         movieId = id
         viewModelScope.launch {
             try {
-                val movie =  moviesConnector.getMovieById.execute(
+                val user = firebaseAuth.currentUser
+                val movie = moviesConnector.getMovieById.execute(
                     id = UUID.fromString(movieId)
                 ).data.movie
-                _uiState.value = MovieDetailUIState.Success(movie)
+
+                _uiState.value = if (user == null) {
+                    MovieDetailUIState.Success(movie, isUserSignedIn = false)
+                } else {
+                    val isWatched = moviesConnector.getIfWatched.execute(
+                        id = user.uid,
+                        movieId = UUID.fromString(movieId)
+                    ).data.watchedMovie != null
+
+                    val isFavorite = moviesConnector.getIfFavoritedMovie.execute(
+                        id = user.uid,
+                        movieId = UUID.fromString(movieId)
+                    ).data.favoriteMovie != null
+
+                    MovieDetailUIState.Success(
+                        movie = movie,
+                        isUserSignedIn = true,
+                        isWatched = isWatched,
+                        isFavorite = isFavorite
+                    )
+                }
+            } catch (e: Exception) {
+                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
+
+    fun toggleFavorite(newValue: Boolean) {
+        viewModelScope.launch {
+            try {
+                if (newValue) {
+                    moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieId))
+                } else {
+                    // TODO(thatfiredev): investigate whether this is a schema error
+                    //    userId probably shouldn't be here.
+                    moviesConnector.deleteFavoritedMovie.execute(
+                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        movieId = UUID.fromString(movieId)
+                    )
+                }
+                // Re-run the query to fetch movie
+                setMovieId(movieId)
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
         }
     }
 
-    fun addToFavorite() {
+    fun toggleWatched(newValue: Boolean) {
         viewModelScope.launch {
             try {
-                moviesConnector.addFavoritedMovie.execute(UUID.fromString(movieId))
+                if (newValue) {
+                    moviesConnector.addWatchedMovie.execute(UUID.fromString(movieId))
+                } else {
+                    // TODO(thatfiredev): investigate whether this is a schema error
+                    //    userId probably shouldn't be here.
+                    moviesConnector.deleteWatchedMovie.execute(
+                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        movieId = UUID.fromString(movieId)
+                    )
+                }
+                // Re-run the query to fetch movie
+                setMovieId(movieId)
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index e24206ecd..9faccc0b7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -86,7 +86,10 @@ class ProfileViewModel(
                 val user = moviesConnector.getUserById.execute(id = userId).data.user
                 _uiState.value = ProfileUIState.ProfileState(
                     user?.username,
-                    favoriteMovies = user?.favoriteMovies ?: emptyList()
+                    favoriteMovies = user?.favoriteMovies ?: emptyList(),
+                    watchedMovies = user?.watched ?: emptyList(),
+                    favoriteActors = user?.favoriteActors ?: emptyList(),
+                    reviews = user?.reviews ?: emptyList()
                 )
                 Log.d("DisplayUser", "$user")
             } catch (e: Exception) {

From 7fa780347938471d155b43e236241f44c7680271 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 18 Sep 2024 04:04:38 +0100
Subject: [PATCH 30/63] refactor: move AuthScreen into its own file

---
 .../dataconnect/feature/profile/AuthScreen.kt | 94 ++++++++++++++++++
 .../feature/profile/ProfileScreen.kt          | 99 ++-----------------
 .../feature/profile/ProfileUIState.kt         |  2 +-
 .../feature/profile/ProfileViewModel.kt       |  5 +-
 4 files changed, 103 insertions(+), 97 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/AuthScreen.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/AuthScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/AuthScreen.kt
new file mode 100644
index 000000000..a9cf6e1df
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/AuthScreen.kt
@@ -0,0 +1,94 @@
+package com.google.firebase.example.dataconnect.feature.profile
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun AuthScreen(
+    onSignUp: (email: String, password: String, displayName: String) -> Unit,
+    onSignIn: (email: String, password: String) -> Unit,
+) {
+    var isSignUp by remember { mutableStateOf(false) }
+    var email by remember { mutableStateOf("") }
+    var password by remember { mutableStateOf("") }
+    var displayName by remember { mutableStateOf("") }
+
+    Column(
+        modifier = Modifier.fillMaxSize(),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        OutlinedTextField(
+            value = email,
+            onValueChange = { email = it },
+            label = { Text("Email") }
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        OutlinedTextField(
+            value = password,
+            onValueChange = { password = it },
+            label = { Text("Password") },
+            visualTransformation = PasswordVisualTransformation()
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        if (isSignUp) {
+            OutlinedTextField(
+                value = displayName,
+                onValueChange = { displayName = it },
+                label = { Text("Name") }
+            )
+        }
+        Spacer(modifier = Modifier.height(16.dp))
+        Button(onClick = {
+            if (isSignUp) {
+                onSignUp(email, password, displayName)
+            } else {
+                onSignIn(email, password)
+            }
+        }) {
+            Text(
+                text = if (isSignUp) {
+                    "Sign up"
+                } else {
+                    "Sign in"
+                }
+            )
+        }
+
+        Spacer(modifier = Modifier.height(16.dp))
+        Text(
+            text = if (isSignUp) {
+                "Already have an account?"
+            } else {
+                "Don't have an account?"
+            }
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        Button(onClick = {
+            isSignUp = !isSignUp
+        }) {
+            Text(
+                text = if (isSignUp) {
+                    "Sign in"
+                } else {
+                    "Sign up"
+                }
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index e6059823e..5927fa498 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -1,32 +1,24 @@
 package com.google.firebase.example.dataconnect.feature.profile
 
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.dataconnect.movies.GetUserByIdQuery
@@ -44,7 +36,7 @@ fun ProfileScreen(
             Text((uiState as ProfileUIState.Error).errorMessage)
         }
 
-        is ProfileUIState.SignUpState -> {
+        is ProfileUIState.AuthState -> {
             AuthScreen(
                 onSignUp = { email, password, displayName ->
                     profileViewModel.signUp(email, password, displayName)
@@ -56,13 +48,13 @@ fun ProfileScreen(
         }
 
         is ProfileUIState.ProfileState -> {
-            val uiState = uiState as ProfileUIState.ProfileState
+            val ui = uiState as ProfileUIState.ProfileState
             ProfileScreen(
-                uiState.username ?: "User",
-                uiState.reviews,
-                uiState.watchedMovies,
-                uiState.favoriteMovies,
-                uiState.favoriteActors,
+                ui.username ?: "User",
+                ui.reviews,
+                ui.watchedMovies,
+                ui.favoriteMovies,
+                ui.favoriteActors,
                 onSignOut = {
                     profileViewModel.signOut()
                 }
@@ -200,81 +192,4 @@ fun FavoriteActorsList(actors: List<GetUserByIdQuery.Data.User.FavoriteActorsIte
     }
 }
 
-@Composable
-fun AuthScreen(
-    onSignUp: (email: String, password: String, displayName: String) -> Unit,
-    onSignIn: (email: String, password: String) -> Unit,
-) {
-    var isSignUp by remember { mutableStateOf(false) }
-    var email by remember { mutableStateOf("") }
-    var password by remember { mutableStateOf("") }
-    var displayName by remember { mutableStateOf("") }
-
-    Column(
-        modifier = Modifier.fillMaxSize(),
-        verticalArrangement = Arrangement.Center,
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-        OutlinedTextField(
-            value = email,
-            onValueChange = { email = it },
-            label = { Text("Email") }
-        )
-        Spacer(modifier = Modifier.height(8.dp))
-        OutlinedTextField(
-            value = password,
-            onValueChange = { password = it },
-            label = { Text("Password") },
-            visualTransformation = PasswordVisualTransformation()
-        )
-        Spacer(modifier = Modifier.height(8.dp))
-        if (isSignUp) {
-            OutlinedTextField(
-                value = displayName,
-                onValueChange = { displayName = it },
-                label = { Text("Name") }
-            )
-        }
-        Spacer(modifier = Modifier.height(16.dp))
-        Button(onClick = {
-            if (isSignUp) {
-                onSignUp(email, password, displayName)
-            } else {
-                onSignIn(email, password)
-            }
-        }) {
-            Text(
-                text = if (isSignUp) {
-                    "Sign up"
-                } else {
-                    "Sign in"
-                }
-            )
-        }
-//        Spacer(modifier = Modifier.height(8.dp))
-//        Button(onClick = { /* Handle Google Sign-in */ }) {
-//            Text("Sign in with Google")
-//        }
 
-        Spacer(modifier = Modifier.height(16.dp))
-        Text(
-            text = if (isSignUp) {
-                "Already have an account?"
-            } else {
-                "Don't have an account?"
-            }
-        )
-        Spacer(modifier = Modifier.height(8.dp))
-        Button(onClick = {
-            isSignUp = !isSignUp
-        }) {
-            Text(
-                text = if (isSignUp) {
-                    "Sign in"
-                } else {
-                    "Sign up"
-                }
-            )
-        }
-    }
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
index df60528fb..a86ce34b7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
@@ -7,7 +7,7 @@ sealed class ProfileUIState {
 
     data class Error(val errorMessage: String): ProfileUIState()
 
-    data object SignUpState: ProfileUIState()
+    data object AuthState: ProfileUIState()
 
     data class ProfileState(
         val username: String?,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index 9faccc0b7..1fe8c75e4 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -3,17 +3,14 @@ package com.google.firebase.example.dataconnect.feature.profile
 import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.Firebase
 import com.google.firebase.auth.FirebaseAuth
 import com.google.firebase.auth.FirebaseAuth.AuthStateListener
 import com.google.firebase.auth.UserProfileChangeRequest
 import com.google.firebase.auth.auth
-import com.google.firebase.auth.userProfileChangeRequest
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
-import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailUIState
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
@@ -36,7 +33,7 @@ class ProfileViewModel(
                 if (currentUser != null) {
                     displayUser(currentUser.uid)
                 } else {
-                    _uiState.value = ProfileUIState.SignUpState
+                    _uiState.value = ProfileUIState.AuthState
                 }
             }
         }

From 609e5040ba3f5e8ea85932067fde3f9fa93e2104 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Thu, 19 Sep 2024 16:52:07 +0100
Subject: [PATCH 31/63] docs: add a README file

---
 dataconnect/README.md | 74 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 dataconnect/README.md

diff --git a/dataconnect/README.md b/dataconnect/README.md
new file mode 100644
index 000000000..53057a5d7
--- /dev/null
+++ b/dataconnect/README.md
@@ -0,0 +1,74 @@
+# Firebase Data Connect Quickstart
+
+## Introduction
+
+This quickstart is a movie review app to demonstrate the use of Firebase Data Connect
+ with a Cloud SQL database.
+For more information about Firebase Data Connect visit [the docs](https://firebase.google.com/docs/data-connect/).
+
+## Getting Started
+
+Follow these steps to get up and running with Firebase Data Connect. For more detailed instructions,
+check out the [official documentation](https://firebase.google.com/docs/data-connect/quickstart).
+
+### 1. Create a New Data Connect Service and Cloud SQL Instance
+
+1. Open [Firebase Data Connect](https://console.firebase.google.com/u/0/project/_/dataconnect) in 
+ your project in Firebase Console and select Get Started.
+2. Create a new Data Connect service and a Cloud SQL instance. Ensure the Blaze plan is active.
+ Pricing details can be found at [Firebase Pricing](https://firebase.google.com/pricing).
+3. Select your server region, if you wish to use vector search, make sure to select `us-central1` region.
+4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance 
+ can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
+
+### 2. Set Up Firebase CLI
+
+Ensure the Firebase CLI is installed and up to date:
+
+```bash
+npm install -g firebase-tools
+```
+
+### 3. Cloning the repository
+This repository contains the quickstart to get started with the functionalities of Data Connect.
+
+1. Clone this repository to your local machine.
+1. (Private Preview only) Checkout the `fdc-quickstart` branch and open the project in Android Studio.
+1. Open the a terminal window and initialize your Firebase project with `firebase init dataconnect`.
+1. Overwrite only dataconnect.yaml when prompted, do not overwrite any other dataconnect files.
+   (Optional): If you intend on using other Firebase features, run `firebase init` instead, and select both DataConnect options as well as any feature you intend to use.
+1. Allow domains for Firebase Auth in your [project console](https://console.firebase.google.com/project/_/authentication/settings) (e.g. http://127.0.0.1).
+
+### 4. Running queries and mutations in VS Code
+The VSCode Firebase Extension allows you to generate Firebase Data Connect SDK code, run queries/mutations, and deploy Firebase Data Connect with a click. Alternatively, see below for CLI commands.
+
+1. Install [VS Code](https://code.visualstudio.com/).
+2. Download the [Firebase extension](https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/vsix%2Ffirebase-vscode-latest.vsix?alt=media) and [install](https://code.visualstudio.com/docs/editor/extension-marketplace#_install-an-extension) it.
+3. Open this quickstart in VS code, and in the left pane of the Firebase extension, and log in with your Firebase account.
+   (Optional): If your Firebase project was not initialized in the last section, you can click `Run firebase init` and select `Data Connect` to initialize.
+4. Click on deploy to deploy your schema to your cloud SQL instance. Or run `firebase deploy --only dataconnect` (this will also activate vectors search if it's enabled in the schema).
+5. Running the VSCode extension should automatically start the DataConnect emulators. If you see an emulators error, try running `firebase emulators:start dataconnect` manually.
+
+Now you should be able to deploy your schema, run mutations/queries, generate SDK code, and view your application locally.
+
+### 5. Populating the database
+1. Run `1_movie_insert.gql`, `2_actor_insert.gql`, `3_movie_actor_insert.gql`, and `4_user_favorites_review_insert.gql` files in the `./dataconnect` directory in order using the VS code extension,
+
+### 6. Running the app
+
+Press the Run button in Android Studio to run the sample app on your device.
+
+## 🚧 Work in Progress
+
+This app is still missing some features which will be added before Public Preview:
+
+- [ ] Search
+- [ ] Movie review
+  - [ ] Add a new review 
+  - [ ] Update a review
+  - [ ] Delete a review
+- [ ] Actors
+  - [ ] Show actor profile
+  - [ ] Mark actor as favorite
+- [ ] Error handling
+  - (Some errors may cause the app to crash at the moment) 

From 30a904b7b66c4e06d9e10e3cbd321c1cd76bc984 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Thu, 19 Sep 2024 17:58:14 +0100
Subject: [PATCH 32/63] feat: support reviewing movies :)

---
 dataconnect/README.md                         |  2 +-
 .../feature/moviedetail/MovieDetailScreen.kt  | 45 +++++++++----------
 .../moviedetail/MovieDetailViewModel.kt       | 20 +++++++++
 3 files changed, 42 insertions(+), 25 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 53057a5d7..d7cf5dd44 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -64,7 +64,7 @@ This app is still missing some features which will be added before Public Previe
 
 - [ ] Search
 - [ ] Movie review
-  - [ ] Add a new review 
+  - [x] Add a new review 
   - [ ] Update a review
   - [ ] Delete a review
 - [ ] Actors
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 6695c101a..f502030e1 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -27,12 +27,14 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Slider
 import androidx.compose.material3.SuggestionChip
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -55,18 +57,7 @@ fun MovieDetailScreen(
 ) {
     movieDetailViewModel.setMovieId(movieId)
     val uiState by movieDetailViewModel.uiState.collectAsState()
-    // TODO: Create a movie favorited toggle
-    Scaffold(
-//        floatingActionButton = {
-//            FloatingActionButton(
-//                onClick = {
-//                    movieDetailViewModel.addToFavorite()
-//                }
-//            ) {
-//                Icon(Icons.Filled.FavoriteBorder, "Favorite")
-//            }
-//        }
-    ) { padding ->
+    Scaffold { padding ->
         when (uiState) {
             is MovieDetailUIState.Error -> {
                 ErrorMessage((uiState as MovieDetailUIState.Error).errorMessage)
@@ -103,8 +94,8 @@ fun MovieDetailScreen(
                     MainActorsList(movie?.mainActors ?: emptyList())
                     SupportingActorsList(movie?.supportingActors ?: emptyList())
                     UserReviews(
-                        onReviewSubmitted = {
-
+                        onReviewSubmitted = { rating, text ->
+                            movieDetailViewModel.addRating(rating, text)
                         },
                         movie?.reviews ?: emptyList()
                     )
@@ -115,14 +106,6 @@ fun MovieDetailScreen(
     }
 }
 
-@Composable
-fun MovieDetailScreen(
-    modifier: Modifier = Modifier,
-    uiState: MovieDetailUIState
-) {
-
-}
-
 @Composable
 fun MovieInformation(
     modifier: Modifier = Modifier,
@@ -272,7 +255,7 @@ fun SupportingActorsList(
 
 @Composable
 fun UserReviews(
-    onReviewSubmitted: (String) -> Unit,
+    onReviewSubmitted: (rating: Float, text: String) -> Unit,
     reviews: List<GetMovieByIdQuery.Data.Movie.ReviewsItem>
 ) {
     var reviewText by remember { mutableStateOf("") }
@@ -288,6 +271,15 @@ fun UserReviews(
             .padding(16.dp),
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
+        var rating by remember { mutableFloatStateOf(3f) }
+        Text("Rating: ${rating}")
+        Slider(
+            value = rating,
+            // Round the value to the nearest 0.5
+            onValueChange = { rating = (Math.round(it * 2) / 2.0).toFloat() },
+            steps = 9,
+            valueRange = 1f..5f
+        )
         TextField(
             value = reviewText,
             onValueChange = { reviewText = it },
@@ -297,7 +289,12 @@ fun UserReviews(
 
         Spacer(modifier = Modifier.height(16.dp))
 
-        Button(onClick = { onReviewSubmitted(reviewText) }) {
+        Button(
+            onClick = {
+                onReviewSubmitted(rating, reviewText)
+                reviewText = ""
+            }
+        ) {
             Text("Submit Review")
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index 5ace553be..929c2225e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -9,6 +9,7 @@ import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
 import java.util.UUID
+import kotlin.math.roundToInt
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.update
@@ -100,4 +101,23 @@ class MovieDetailViewModel(
             }
         }
     }
+
+    fun addRating(rating: Float, text: String) {
+        viewModelScope.launch {
+            try {
+                moviesConnector.addReview.execute(
+                    movieId = UUID.fromString(movieId),
+                    // TODO(thatfiredev): this might have been an error in the mutation definition
+                    //   rating shouldn't be an Int!!
+                    rating = rating.roundToInt(),
+                    reviewText = text
+                )
+                // TODO(thatfiredev): should we have a way of only refetching the reviews?
+                // Re-run the query to fetch movie
+                setMovieId(movieId)
+            } catch (e: Exception) {
+                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
 }
\ No newline at end of file

From 14034c6085d6b3990752c8ddb12f7098731af409 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 01:41:07 +0100
Subject: [PATCH 33/63] feat: create actor details screen

---
 dataconnect/README.md                         |   8 +-
 .../example/dataconnect/MainActivity.kt       |  11 +-
 .../feature/actordetail/ActorDetailScreen.kt  | 209 ++++++++++++++++++
 .../feature/actordetail/ActorDetailUIState.kt |  18 ++
 .../actordetail/ActorDetailViewModel.kt       |  73 ++++++
 .../feature/actordetail/Navigation.kt         |  26 +++
 .../feature/genredetail/GenreDetailScreen.kt  |   4 +-
 .../feature/moviedetail/MovieDetailScreen.kt  |  21 +-
 .../feature/moviedetail/Navigation.kt         |   6 +-
 .../feature/movies/MoviesScreen.kt            |   4 +-
 .../feature/profile/ProfileScreen.kt          |  12 +-
 .../dataconnect/ui/components/ActorTile.kt    |  18 +-
 .../dataconnect/ui/components/MovieTile.kt    |  22 +-
 .../app/src/main/res/values/strings.xml       |   1 +
 14 files changed, 398 insertions(+), 35 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt

diff --git a/dataconnect/README.md b/dataconnect/README.md
index d7cf5dd44..d363b080a 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -67,8 +67,8 @@ This app is still missing some features which will be added before Public Previe
   - [x] Add a new review 
   - [ ] Update a review
   - [ ] Delete a review
-- [ ] Actors
-  - [ ] Show actor profile
-  - [ ] Mark actor as favorite
+- [x] Actors
+  - [x] Show actor profile
+  - [x] Mark actor as favorite
 - [ ] Error handling
-  - (Some errors may cause the app to crash at the moment) 
+  Some errors may cause the app to crash, especially if there's no user logged in. 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index a0d8bdd30..6beab2c7e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -27,6 +27,8 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
+import com.google.firebase.example.dataconnect.feature.actordetail.actorDetailScreen
+import com.google.firebase.example.dataconnect.feature.actordetail.navigateToActorDetail
 import com.google.firebase.example.dataconnect.feature.genredetail.genreDetailScreen
 import com.google.firebase.example.dataconnect.feature.genredetail.navigateToGenreDetail
 import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
@@ -105,7 +107,14 @@ class MainActivity : ComponentActivity() {
                                 launchSingleTop = true
                             }
                         })
-                        movieDetailScreen()
+                        movieDetailScreen(
+                            onActorClicked = { actorId ->
+                                navController.navigateToActorDetail(actorId) {
+                                    launchSingleTop = true
+                                }
+                            }
+                        )
+                        actorDetailScreen()
                         genresScreen(onGenreClicked = { genre ->
                             navController.navigateToGenreDetail(genre) {
                                 launchSingleTop = true
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
new file mode 100644
index 000000000..8d429970b
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -0,0 +1,209 @@
+package com.google.firebase.example.dataconnect.feature.actordetail
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.outlined.FavoriteBorder
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.AsyncImage
+import com.google.firebase.dataconnect.movies.GetActorByIdQuery
+import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.feature.moviedetail.ErrorMessage
+import com.google.firebase.example.dataconnect.ui.components.ActorTile
+import com.google.firebase.example.dataconnect.ui.components.MovieTile
+
+@Composable
+fun ActorDetailScreen(
+    actorId: String,
+    actorDetailViewModel: ActorDetailViewModel = viewModel()
+) {
+    actorDetailViewModel.setActorId(actorId)
+    val uiState by actorDetailViewModel.uiState.collectAsState()
+    Scaffold { innerPadding ->
+        when (uiState) {
+            is ActorDetailUIState.Error -> {
+                ErrorMessage((uiState as ActorDetailUIState.Error).errorMessage)
+            }
+
+            ActorDetailUIState.Loading -> {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier.fillMaxSize()
+                ) {
+                    CircularProgressIndicator()
+                }
+            }
+
+            is ActorDetailUIState.Success -> {
+                val ui = uiState as ActorDetailUIState.Success
+                val scrollState = rememberScrollState()
+                Column(
+                    modifier = Modifier.verticalScroll(scrollState)
+                ) {
+                    ActorInformation(
+                        modifier = Modifier.padding(innerPadding),
+                        actor = ui.actor,
+                        isActorFavorite = ui.isFavorite,
+                        onFavoriteToggled = {
+                            actorDetailViewModel.toggleFavorite(it)
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun ActorInformation(
+    modifier: Modifier = Modifier,
+    actor: GetActorByIdQuery.Data.Actor?,
+    isActorFavorite: Boolean,
+    onFavoriteToggled: (newValue: Boolean) -> Unit
+) {
+    if (actor == null) {
+        ErrorMessage(stringResource(R.string.error_movie_not_found))
+    } else {
+        Column(
+            modifier = modifier
+                .padding(16.dp)
+        ) {
+            Text(
+                text = actor.name,
+                style = MaterialTheme.typography.headlineLarge
+            )
+            Row {
+                AsyncImage(
+                    model = actor.imageUrl,
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,
+                    modifier = Modifier
+                        .width(150.dp)
+                        .aspectRatio(9f / 16f)
+                        .padding(vertical = 8.dp)
+                )
+                Column(
+                    modifier = Modifier.padding(horizontal = 16.dp)
+                ) {
+                    Text(
+                        text = actor.biography ?: stringResource(R.string.biography_not_available),
+                        modifier = Modifier.fillMaxWidth()
+                    )
+                }
+            }
+            Spacer(modifier = Modifier.height(8.dp))
+            Row {
+                Spacer(modifier = Modifier.width(8.dp))
+                if (isActorFavorite) {
+                    FilledTonalButton(onClick = {
+                        onFavoriteToggled(false)
+                    }) {
+                        Icon(Icons.Filled.Favorite, "Favorite")
+                        Text("Favorite", modifier = Modifier.padding(start = 4.dp))
+                    }
+                } else {
+                    OutlinedButton(onClick = {
+                        onFavoriteToggled(true)
+                    }) {
+                        Icon(Icons.Outlined.FavoriteBorder, "Favorite")
+                        Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
+                    }
+                }
+            }
+            Spacer(modifier = Modifier.height(8.dp))
+            MainRoles(
+                movies = actor.mainActors,
+                onMovieClicked = { movieId ->
+                    // TODO(thatfiredev): Support navigating to movie
+                }
+            )
+            SupportingRoles(
+                movies = actor.supportingActors,
+                onMovieClicked = { movieId ->
+                    // TODO(thatfiredev): Support navigating to movie
+                }
+            )
+        }
+    }
+}
+
+@Composable
+fun MainRoles(
+    movies: List<GetActorByIdQuery.Data.Actor.MainActorsItem?>,
+    onMovieClicked: (movieId: String) -> Unit
+) {
+    Text(
+        text = "Main Roles",
+        style = MaterialTheme.typography.headlineSmall
+    )
+    Spacer(modifier = Modifier.height(4.dp))
+    LazyRow {
+        items(movies) { movie ->
+            movie?.let {
+                MovieTile(
+                    movieId = it.id.toString(),
+                    movieTitle = it.title,
+                    movieImageUrl = it.imageUrl,
+                    tileWidth = 120.dp,
+                    onMovieClicked = onMovieClicked,
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun SupportingRoles(
+    movies: List<GetActorByIdQuery.Data.Actor.SupportingActorsItem?>,
+    onMovieClicked: (movieId: String) -> Unit
+) {
+    Text(
+        text = "Supporting Roles",
+        style = MaterialTheme.typography.headlineSmall,
+        modifier = Modifier.padding(horizontal = 8.dp)
+    )
+    Spacer(modifier = Modifier.height(4.dp))
+    LazyRow(
+        modifier = Modifier.padding(horizontal = 8.dp)
+    ) {
+        items(movies) { movie ->
+            movie?.let {
+                MovieTile(
+                    movieId = it.id.toString(),
+                    movieTitle = it.title,
+                    movieImageUrl = it.imageUrl,
+                    tileWidth = 120.dp,
+                    onMovieClicked = onMovieClicked,
+                )
+            }
+        }
+    }
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
new file mode 100644
index 000000000..089ddb1b7
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
@@ -0,0 +1,18 @@
+package com.google.firebase.example.dataconnect.feature.actordetail
+
+import com.google.firebase.dataconnect.movies.GetActorByIdQuery
+import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
+
+
+sealed class ActorDetailUIState {
+    data object Loading: ActorDetailUIState()
+
+    data class Error(val errorMessage: String): ActorDetailUIState()
+
+    data class Success(
+        // Actor is null if it can't be found on the DB
+        val actor: GetActorByIdQuery.Data.Actor?,
+        val isUserSignedIn: Boolean = false,
+        var isFavorite: Boolean = false
+    ) : ActorDetailUIState()
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
new file mode 100644
index 000000000..ae6877a8b
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
@@ -0,0 +1,73 @@
+package com.google.firebase.example.dataconnect.feature.actordetail
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.firebase.Firebase
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.auth
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.execute
+import com.google.firebase.dataconnect.movies.instance
+import java.util.UUID
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+class ActorDetailViewModel(
+    private val firebaseAuth: FirebaseAuth = Firebase.auth,
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+) : ViewModel() {
+    private var actorId: String = ""
+
+    private val _uiState = MutableStateFlow<ActorDetailUIState>(ActorDetailUIState.Loading)
+    val uiState: StateFlow<ActorDetailUIState>
+        get() = _uiState
+
+    fun setActorId(id: String) {
+        actorId = id
+        viewModelScope.launch {
+            try {
+                val user = firebaseAuth.currentUser
+                val actor = moviesConnector.getActorById.execute(
+                    id = UUID.fromString(actorId)
+                ).data.actor
+
+                _uiState.value = if (user == null) {
+                    ActorDetailUIState.Success(actor, isUserSignedIn = false)
+                } else {
+                    val isFavorite = moviesConnector.getIfFavoritedActor.execute(
+                        id = user.uid,
+                        actorId = UUID.fromString(actorId)
+                    ).data.favoriteActor != null
+
+                    ActorDetailUIState.Success(
+                        actor,
+                        isUserSignedIn = true,
+                        isFavorite = isFavorite
+                    )
+                }
+            } catch (e: Exception) {
+                _uiState.value = ActorDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
+
+    fun toggleFavorite(newValue: Boolean) {
+        viewModelScope.launch {
+            try {
+                if (newValue) {
+                    moviesConnector.addFavoritedActor.execute(UUID.fromString(actorId))
+                } else {
+                    moviesConnector.deleteFavoriteActor.execute(
+                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        actorId = UUID.fromString(actorId)
+                    )
+                }
+                // Re-run the query to fetch the actor details
+                setActorId(actorId)
+            } catch (e: Exception) {
+                _uiState.value = ActorDetailUIState.Error(e.message ?: "")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt
new file mode 100644
index 000000000..152c75971
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt
@@ -0,0 +1,26 @@
+package com.google.firebase.example.dataconnect.feature.actordetail
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptionsBuilder
+import androidx.navigation.compose.composable
+
+const val ACTOR_DETAIL_ROUTE = "actors/{actor}"
+
+fun NavController.navigateToActorDetail(
+    actorId: String,
+    navOptions: NavOptionsBuilder.() -> Unit = { }
+) = navigate(ACTOR_DETAIL_ROUTE.replace("{actor}", actorId), navOptions)
+
+fun NavGraphBuilder.actorDetailScreen() {
+    composable(
+        route = ACTOR_DETAIL_ROUTE
+    ) { navBackStackEntry ->
+        navBackStackEntry.arguments?.let {
+            val actorId = it.getString("actor")
+            actorId?.let { id ->
+                ActorDetailScreen(id)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 22a4c8f4e..69e2010d5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -74,7 +74,7 @@ fun GenreDetailScreen(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
                             movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating ?: 0.0,
+                            movieRating = movie.rating,
                             onMovieClicked = {
                                 // TODO
                             }
@@ -93,7 +93,7 @@ fun GenreDetailScreen(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
                             movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating ?: 0.0,
+                            movieRating = movie.rating,
                             onMovieClicked = {
                                 // TODO(thatfiredev)
                             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index f502030e1..8513b6f93 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -53,6 +53,7 @@ import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 @Composable
 fun MovieDetailScreen(
     movieId: String,
+    onActorClicked: (actorId: String) -> Unit,
     movieDetailViewModel: MovieDetailViewModel = viewModel()
 ) {
     movieDetailViewModel.setMovieId(movieId)
@@ -91,8 +92,14 @@ fun MovieDetailScreen(
                             movieDetailViewModel.toggleWatched(newValue)
                         }
                     )
-                    MainActorsList(movie?.mainActors ?: emptyList())
-                    SupportingActorsList(movie?.supportingActors ?: emptyList())
+                    MainActorsList(
+                        movie?.mainActors ?: emptyList(),
+                        onActorClicked
+                    )
+                    SupportingActorsList(
+                        movie?.supportingActors ?: emptyList(),
+                        onActorClicked
+                    )
                     UserReviews(
                         onReviewSubmitted = { rating, text ->
                             movieDetailViewModel.addRating(rating, text)
@@ -213,7 +220,8 @@ fun MovieInformation(
 
 @Composable
 fun MainActorsList(
-    actors: List<GetMovieByIdQuery.Data.Movie.MainActorsItem?>
+    actors: List<GetMovieByIdQuery.Data.Movie.MainActorsItem?>,
+    onActorClicked: (actorId: String) -> Unit
 ) {
     Text(
         text = "Main Actors",
@@ -226,7 +234,7 @@ fun MainActorsList(
     ) {
         items(actors) { actor ->
             actor?.let {
-                ActorTile(it.name, it.imageUrl)
+                ActorTile(it.id.toString(), it.name, it.imageUrl, onActorClicked)
             }
         }
     }
@@ -234,7 +242,8 @@ fun MainActorsList(
 
 @Composable
 fun SupportingActorsList(
-    actors: List<GetMovieByIdQuery.Data.Movie.SupportingActorsItem?>
+    actors: List<GetMovieByIdQuery.Data.Movie.SupportingActorsItem?>,
+    onActorClicked: (actorId: String) -> Unit,
 ) {
     Text(
         text = "Supporting Actors",
@@ -247,7 +256,7 @@ fun SupportingActorsList(
     ) {
         items(actors) { actor ->
             actor?.let {
-                ActorTile(it.name, it.imageUrl)
+                ActorTile(it.id.toString(), it.name, it.imageUrl, onActorClicked)
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
index b80048200..f1f9075b6 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
@@ -12,14 +12,16 @@ fun NavController.navigateToMovieDetail(
     navOptions: NavOptionsBuilder.() -> Unit = { }
 ) = navigate(MOVIE_DETAIL_ROUTE.replace("{movie}", movieId), navOptions)
 
-fun NavGraphBuilder.movieDetailScreen() {
+fun NavGraphBuilder.movieDetailScreen(
+    onActorClicked: (actorId: String) -> Unit
+) {
     composable(
         route = MOVIE_DETAIL_ROUTE
     ) { backStackEntry ->
         backStackEntry.arguments?.let {
             val movieId = it.getString("movie")
             movieId?.let { id ->
-                MovieDetailScreen(id)
+                MovieDetailScreen(id, onActorClicked)
             }
 
         }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index 3fc75dce5..9f8430815 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -70,7 +70,7 @@ fun MoviesScreen(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
                             movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating ?: 0.0,
+                            movieRating = movie.rating,
                             onMovieClicked = {
                                 onMovieClicked(movie.id.toString())
                             }
@@ -89,7 +89,7 @@ fun MoviesScreen(
                             movieId = movie.id.toString(),
                             movieTitle = movie.title,
                             movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating ?: 0.0,
+                            movieRating = movie.rating,
                             onMovieClicked = {
                                 onMovieClicked(movie.id.toString())
                             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 5927fa498..8582bb096 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -154,7 +154,8 @@ fun WatchedMoviesList(watchedItems: List<GetUserByIdQuery.Data.User.WatchedItem>
                 movieId = watchedItem.movie.id.toString(),
                 movieImageUrl = watchedItem.movie.imageUrl,
                 movieTitle = watchedItem.movie.title,
-                movieRating = watchedItem.movie.rating ?: 0.0,
+                movieRating = watchedItem.movie.rating,
+                tileWidth = 120.dp,
                 onMovieClicked = {
                     // TODO
                 }
@@ -171,7 +172,8 @@ fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMo
                 movieId = favoriteItem.movie.id.toString(),
                 movieImageUrl = favoriteItem.movie.imageUrl,
                 movieTitle = favoriteItem.movie.title,
-                movieRating = favoriteItem.movie.rating ?: 0.0,
+                movieRating = favoriteItem.movie.rating,
+                tileWidth = 120.dp,
                 onMovieClicked = {
                     // TODO
                 }
@@ -185,8 +187,12 @@ fun FavoriteActorsList(actors: List<GetUserByIdQuery.Data.User.FavoriteActorsIte
     LazyRow {
         items(actors) { favoriteActor ->
             ActorTile(
+                actorId = favoriteActor.actor.id.toString(),
                 actorName = favoriteActor.actor.name,
-                actorImageUrl = favoriteActor.actor.imageUrl
+                actorImageUrl = favoriteActor.actor.imageUrl,
+                onActorClicked = {
+                    // TODO
+                }
             )
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
index 3a972ae69..d6d135100 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
@@ -1,6 +1,7 @@
 package com.google.firebase.example.dataconnect.ui.components
 
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
@@ -28,15 +29,22 @@ val ACTOR_CARD_SIZE = 80.dp
 
 @Composable
 fun ActorTile(
+    actorId: String,
     actorName: String,
-    actorImageUrl: String
+    actorImageUrl: String,
+    onActorClicked: (actorId: String) -> Unit
 ) {
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = Modifier.sizeIn(
-            maxWidth = ACTOR_CARD_SIZE,
-            maxHeight = ACTOR_CARD_SIZE + 32.dp
-        ).padding(4.dp)
+        modifier = Modifier
+            .sizeIn(
+                maxWidth = ACTOR_CARD_SIZE,
+                maxHeight = ACTOR_CARD_SIZE + 32.dp
+            )
+            .padding(4.dp)
+            .clickable {
+                onActorClicked(actorId)
+            }
     ) {
         AsyncImage(
             model = actorImageUrl,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
index 70f7ecba7..3939d3a92 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
@@ -17,24 +17,24 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 
-val MAX_MOVIE_CARD_WIDTH = 150.dp
-
 @Composable
 fun MovieTile(
     modifier: Modifier = Modifier,
+    tileWidth: Dp = 150.dp,
     movieId: String,
     movieImageUrl: String,
     movieTitle: String,
-    movieRating: Double,
+    movieRating: Double? = null,
     onMovieClicked: (movieId: String) -> Unit
 ) {
     Card(
         modifier = modifier
             .padding(vertical = 16.dp, horizontal = 4.dp)
-            .sizeIn(maxWidth = MAX_MOVIE_CARD_WIDTH)
+            .sizeIn(maxWidth = tileWidth)
             .clickable {
                 onMovieClicked(movieId)
             },
@@ -48,14 +48,16 @@ fun MovieTile(
         Text(
             text = movieTitle,
             style = MaterialTheme.typography.titleMedium,
-            modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp),
+            modifier = Modifier.padding(8.dp),
             maxLines = 1,
             overflow = TextOverflow.Ellipsis
         )
-        Text(
-            text = "Rating: $movieRating",
-            modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
-            style = MaterialTheme.typography.bodySmall
-        )
+        movieRating?.let {
+            Text(
+                text = "Rating: $movieRating",
+                modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
+                style = MaterialTheme.typography.bodySmall
+            )
+        }
     }
 }
\ No newline at end of file
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index e173c48a7..303332967 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -19,4 +19,5 @@
     <!-- Movie Detail Screen -->
     <string name="error_movie_not_found">Couldnt find movie in the database</string>
     <string name="description_not_available">Description not available</string>
+    <string name="biography_not_available">Biography not available</string>
 </resources>
\ No newline at end of file

From dff9533e34ebbd991cdb4831fd3d013457a8ea16 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 02:33:57 +0100
Subject: [PATCH 34/63] refactor: make actor list reusable

---
 .../feature/actordetail/ActorDetailScreen.kt  |  1 -
 .../feature/moviedetail/MovieDetailScreen.kt  | 73 +++++--------------
 .../feature/moviedetail/Navigation.kt         |  1 -
 .../feature/profile/ProfileScreen.kt          | 33 ++++-----
 .../{ActorTile.kt => ActorsList.kt}           | 58 +++++++++++----
 .../app/src/main/res/values/strings.xml       | 18 +++++
 6 files changed, 94 insertions(+), 90 deletions(-)
 rename dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/{ActorTile.kt => ActorsList.kt} (59%)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index 8d429970b..4f23f8b1e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -37,7 +37,6 @@ import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetActorByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.feature.moviedetail.ErrorMessage
-import com.google.firebase.example.dataconnect.ui.components.ActorTile
 import com.google.firebase.example.dataconnect.ui.components.MovieTile
 
 @Composable
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 8513b6f93..9ae2d55f0 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
@@ -47,7 +45,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.ui.components.ActorTile
+import com.google.firebase.example.dataconnect.ui.components.Actor
+import com.google.firebase.example.dataconnect.ui.components.ActorsList
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
 @Composable
@@ -92,13 +91,21 @@ fun MovieDetailScreen(
                             movieDetailViewModel.toggleWatched(newValue)
                         }
                     )
-                    MainActorsList(
-                        movie?.mainActors ?: emptyList(),
-                        onActorClicked
+                    // Main Actors list
+                    ActorsList(
+                        listTitle = stringResource(R.string.title_main_actors),
+                        actors = movie?.mainActors?.mapNotNull {
+                            Actor(it.id.toString(), it.name, it.imageUrl)
+                        }.orEmpty(),
+                        onActorClicked = { onActorClicked(it) }
                     )
-                    SupportingActorsList(
-                        movie?.supportingActors ?: emptyList(),
-                        onActorClicked
+                    // Supporting Actors list
+                    ActorsList(
+                        listTitle = stringResource(R.string.title_supporting_actors),
+                        actors = movie?.supportingActors?.mapNotNull {
+                            Actor(it.id.toString(), it.name, it.imageUrl)
+                        }.orEmpty(),
+                        onActorClicked = { onActorClicked(it) }
                     )
                     UserReviews(
                         onReviewSubmitted = { rating, text ->
@@ -218,50 +225,6 @@ fun MovieInformation(
     }
 }
 
-@Composable
-fun MainActorsList(
-    actors: List<GetMovieByIdQuery.Data.Movie.MainActorsItem?>,
-    onActorClicked: (actorId: String) -> Unit
-) {
-    Text(
-        text = "Main Actors",
-        style = MaterialTheme.typography.headlineMedium,
-        modifier = Modifier.padding(horizontal = 16.dp)
-    )
-    Spacer(modifier = Modifier.height(8.dp))
-    LazyRow(
-        modifier = Modifier.padding(horizontal = 16.dp)
-    ) {
-        items(actors) { actor ->
-            actor?.let {
-                ActorTile(it.id.toString(), it.name, it.imageUrl, onActorClicked)
-            }
-        }
-    }
-}
-
-@Composable
-fun SupportingActorsList(
-    actors: List<GetMovieByIdQuery.Data.Movie.SupportingActorsItem?>,
-    onActorClicked: (actorId: String) -> Unit,
-) {
-    Text(
-        text = "Supporting Actors",
-        style = MaterialTheme.typography.headlineMedium,
-        modifier = Modifier.padding(horizontal = 16.dp)
-    )
-    Spacer(modifier = Modifier.height(8.dp))
-    LazyRow(
-        modifier = Modifier.padding(horizontal = 16.dp)
-    ) {
-        items(actors) { actor ->
-            actor?.let {
-                ActorTile(it.id.toString(), it.name, it.imageUrl, onActorClicked)
-            }
-        }
-    }
-}
-
 @Composable
 fun UserReviews(
     onReviewSubmitted: (rating: Float, text: String) -> Unit,
@@ -292,7 +255,7 @@ fun UserReviews(
         TextField(
             value = reviewText,
             onValueChange = { reviewText = it },
-            label = { Text("Write your review") },
+            label = { Text(stringResource(R.string.hint_write_review)) },
             modifier = Modifier.fillMaxWidth()
         )
 
@@ -304,7 +267,7 @@ fun UserReviews(
                 reviewText = ""
             }
         ) {
-            Text("Submit Review")
+            Text(stringResource(R.string.button_submit_review))
         }
     }
     Column {
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
index f1f9075b6..ecef5023d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
@@ -23,7 +23,6 @@ fun NavGraphBuilder.movieDetailScreen(
             movieId?.let { id ->
                 MovieDetailScreen(id, onActorClicked)
             }
-
         }
     }
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 8582bb096..c9cf11c9f 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -19,10 +19,13 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.dataconnect.movies.GetUserByIdQuery
-import com.google.firebase.example.dataconnect.ui.components.ActorTile
+import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.ui.components.Actor
+import com.google.firebase.example.dataconnect.ui.components.ActorsList
 import com.google.firebase.example.dataconnect.ui.components.MovieTile
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
@@ -108,7 +111,15 @@ fun ProfileScreen(
         ProfileSection(title = "Favorite Movies", content = { FavoriteMoviesList(favoriteMovies) })
         Spacer(modifier = Modifier.height(16.dp))
 
-        ProfileSection(title = "Favorite Actors", content = { FavoriteActorsList(favoriteActors) })
+        ActorsList(
+            listTitle = stringResource(R.string.title_favorite_actors),
+            actors = favoriteActors.mapNotNull {
+                Actor(it.actor.id.toString(), it.actor.name, it.actor.imageUrl)
+            },
+            onActorClicked = {
+                // TODO
+            }
+        )
 
         ProfileSection(title = "Reviews", content = { ReviewsList(name, reviews) })
         Spacer(modifier = Modifier.height(16.dp))
@@ -181,21 +192,3 @@ fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMo
         }
     }
 }
-
-@Composable
-fun FavoriteActorsList(actors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem>) {
-    LazyRow {
-        items(actors) { favoriteActor ->
-            ActorTile(
-                actorId = favoriteActor.actor.id.toString(),
-                actorName = favoriteActor.actor.name,
-                actorImageUrl = favoriteActor.actor.imageUrl,
-                onActorClicked = {
-                    // TODO
-                }
-            )
-        }
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
similarity index 59%
rename from dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
rename to dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
index d6d135100..6b181694d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
@@ -1,16 +1,14 @@
 package com.google.firebase.example.dataconnect.ui.components
 
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -20,18 +18,52 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
-import coil.compose.rememberAsyncImagePainter
 
 val ACTOR_CARD_SIZE = 80.dp
 
+/**
+ * Used to represent an actor in a list UI
+ */
+data class Actor(
+    val id: String,
+    val name: String,
+    val imageUrl: String
+)
+
+/**
+ * Displays a scrollable horizontal list of actors.
+ */
+@Composable
+fun ActorsList(
+    modifier: Modifier = Modifier,
+    listTitle: String,
+    actors: List<Actor>? = emptyList(),
+    onActorClicked: (actorId: String) -> Unit
+) {
+    Column(
+        modifier = modifier.padding(horizontal = 16.dp)
+    ) {
+        Text(
+            text = listTitle,
+            style = MaterialTheme.typography.headlineMedium
+        )
+        Spacer(modifier = Modifier.height(8.dp))
+        LazyRow {
+            items(actors.orEmpty()) { actor ->
+                ActorTile(actor, onActorClicked)
+            }
+        }
+    }
+}
+
+/**
+ * Used to display each actor item in the list.
+ */
 @Composable
 fun ActorTile(
-    actorId: String,
-    actorName: String,
-    actorImageUrl: String,
+    actor: Actor,
     onActorClicked: (actorId: String) -> Unit
 ) {
     Column(
@@ -43,11 +75,11 @@ fun ActorTile(
             )
             .padding(4.dp)
             .clickable {
-                onActorClicked(actorId)
+                onActorClicked(actor.id)
             }
     ) {
         AsyncImage(
-            model = actorImageUrl,
+            model = actor.imageUrl,
             contentDescription = null,
             contentScale = ContentScale.Crop,
             modifier = Modifier
@@ -55,7 +87,7 @@ fun ActorTile(
                 .clip(CircleShape)
         )
         Text(
-            text = actorName,
+            text = actor.name,
             style = MaterialTheme.typography.bodyLarge,
             maxLines = 1,
             overflow = TextOverflow.Ellipsis
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index 303332967..b2e46db97 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -19,5 +19,23 @@
     <!-- Movie Detail Screen -->
     <string name="error_movie_not_found">Couldnt find movie in the database</string>
     <string name="description_not_available">Description not available</string>
+    <string name="button_mark_watched">Mark as watched</string>
+    <string name="button_unmark_watched">Watched</string>
+    <string name="button_favorite">Add to favorites</string>
+    <string name="button_remove_favorite">Favorite</string>
+    <string name="title_main_actors">Main Actors</string>
+    <string name="title_supporting_actors">Supporting Actors</string>
+    <string name="title_user_reviews">User Reviews</string>
+    <string name="hint_write_review">Write your review</string>
+    <string name="button_submit_review">Submit Review</string>
+
+    <!-- Actor Detail Screen -->
     <string name="biography_not_available">Biography not available</string>
+
+    <!-- Profile Screen -->
+    <string name="title_watched_movies">Watched Movies</string>
+    <string name="title_favorite_movies">Favorite Movies</string>
+    <string name="title_favorite_actors">Favorite Actors</string>
+    <string name="title_reviews">Reviews</string>
+
 </resources>
\ No newline at end of file

From 155d7a5b068ca51a9ecb311474ecf9ba40de7d7d Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 02:57:19 +0100
Subject: [PATCH 35/63] refactor: make movies list reusable

---
 .../feature/actordetail/ActorDetailScreen.kt  | 72 ++++---------------
 .../feature/genredetail/GenreDetailScreen.kt  | 56 +++++----------
 .../feature/movies/MoviesScreen.kt            | 63 +++++-----------
 .../feature/profile/ProfileScreen.kt          | 62 ++++++----------
 .../{MovieTile.kt => MoviesList.kt}           | 68 ++++++++++++++----
 .../app/src/main/res/values/strings.xml       |  2 +
 6 files changed, 123 insertions(+), 200 deletions(-)
 rename dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/{MovieTile.kt => MoviesList.kt} (53%)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index 4f23f8b1e..c6166ee1f 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
@@ -37,7 +35,8 @@ import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetActorByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.feature.moviedetail.ErrorMessage
-import com.google.firebase.example.dataconnect.ui.components.MovieTile
+import com.google.firebase.example.dataconnect.ui.components.Movie
+import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
 @Composable
 fun ActorDetailScreen(
@@ -138,14 +137,20 @@ fun ActorInformation(
                 }
             }
             Spacer(modifier = Modifier.height(8.dp))
-            MainRoles(
-                movies = actor.mainActors,
+            MoviesList(
+                listTitle = stringResource(R.string.title_main_roles),
+                movies = actor.mainActors.mapNotNull {
+                    Movie(it.id.toString(), it.imageUrl, it.title)
+                },
                 onMovieClicked = { movieId ->
                     // TODO(thatfiredev): Support navigating to movie
                 }
             )
-            SupportingRoles(
-                movies = actor.supportingActors,
+            MoviesList(
+                listTitle = stringResource(R.string.title_supporting_actors),
+                movies = actor.supportingActors.mapNotNull {
+                    Movie(it.id.toString(), it.imageUrl, it.title)
+                },
                 onMovieClicked = { movieId ->
                     // TODO(thatfiredev): Support navigating to movie
                 }
@@ -153,56 +158,3 @@ fun ActorInformation(
         }
     }
 }
-
-@Composable
-fun MainRoles(
-    movies: List<GetActorByIdQuery.Data.Actor.MainActorsItem?>,
-    onMovieClicked: (movieId: String) -> Unit
-) {
-    Text(
-        text = "Main Roles",
-        style = MaterialTheme.typography.headlineSmall
-    )
-    Spacer(modifier = Modifier.height(4.dp))
-    LazyRow {
-        items(movies) { movie ->
-            movie?.let {
-                MovieTile(
-                    movieId = it.id.toString(),
-                    movieTitle = it.title,
-                    movieImageUrl = it.imageUrl,
-                    tileWidth = 120.dp,
-                    onMovieClicked = onMovieClicked,
-                )
-            }
-        }
-    }
-}
-
-@Composable
-fun SupportingRoles(
-    movies: List<GetActorByIdQuery.Data.Actor.SupportingActorsItem?>,
-    onMovieClicked: (movieId: String) -> Unit
-) {
-    Text(
-        text = "Supporting Roles",
-        style = MaterialTheme.typography.headlineSmall,
-        modifier = Modifier.padding(horizontal = 8.dp)
-    )
-    Spacer(modifier = Modifier.height(4.dp))
-    LazyRow(
-        modifier = Modifier.padding(horizontal = 8.dp)
-    ) {
-        items(movies) { movie ->
-            movie?.let {
-                MovieTile(
-                    movieId = it.id.toString(),
-                    movieTitle = it.title,
-                    movieImageUrl = it.imageUrl,
-                    tileWidth = 120.dp,
-                    onMovieClicked = onMovieClicked,
-                )
-            }
-        }
-    }
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 69e2010d5..9af1540d7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -1,13 +1,9 @@
 package com.google.firebase.example.dataconnect.feature.genredetail
 
-import androidx.compose.foundation.gestures.ScrollableState
-import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.CircularProgressIndicator
@@ -22,7 +18,8 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.ui.components.MovieTile
+import com.google.firebase.example.dataconnect.ui.components.Movie
+import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
 @Composable
 fun GenreDetailScreen(
@@ -63,43 +60,24 @@ fun GenreDetailScreen(
                     text = stringResource(R.string.title_genre_detail, uiState.genreName),
                     style = MaterialTheme.typography.headlineLarge
                 )
-                Text(
-                    text = stringResource(R.string.title_most_popular),
-                    style = MaterialTheme.typography.headlineMedium,
-                    modifier = Modifier.padding(vertical = 16.dp)
-                )
-                LazyRow {
-                    items(uiState.mostPopular) { movie ->
-                        MovieTile(
-                            movieId = movie.id.toString(),
-                            movieTitle = movie.title,
-                            movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating,
-                            onMovieClicked = {
-                                // TODO
-                            }
-                        )
+                MoviesList(
+                    listTitle = stringResource(R.string.title_most_popular),
+                    movies = uiState.mostPopular.mapNotNull {
+                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())
+                    },
+                    onMovieClicked = {
+                        // TODO
                     }
-                }
-
-                Text(
-                    text = stringResource(R.string.title_most_recent),
-                    style = MaterialTheme.typography.headlineMedium,
-                    modifier = Modifier.padding(vertical = 16.dp)
                 )
-                LazyRow {
-                    items(uiState.mostRecent) { movie ->
-                        MovieTile(
-                            movieId = movie.id.toString(),
-                            movieTitle = movie.title,
-                            movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating,
-                            onMovieClicked = {
-                                // TODO(thatfiredev)
-                            }
-                        )
+                MoviesList(
+                    listTitle = stringResource(R.string.title_most_recent),
+                    movies = uiState.mostRecent.mapNotNull {
+                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())
+                    },
+                    onMovieClicked = {
+                        // TODO
                     }
-                }
+                )
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index 9f8430815..ade279b1a 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -1,31 +1,26 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Card
 import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
-import coil.compose.AsyncImage
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.ui.components.MovieTile
+import com.google.firebase.example.dataconnect.ui.components.Movie
+import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
 @Composable
 fun MoviesScreen(
@@ -56,46 +51,24 @@ fun MoviesScreen(
         is MoviesUIState.Success -> {
             val scrollState = rememberScrollState()
             Column(
-                modifier = Modifier.padding(16.dp)
+                modifier = Modifier
                     .verticalScroll(scrollState)
             ) {
-                Text(
-                    text = stringResource(R.string.title_top_10_movies),
-                    style = MaterialTheme.typography.headlineMedium,
-                    modifier = Modifier.padding(bottom = 16.dp)
+                MoviesList(
+                    listTitle = stringResource(R.string.title_top_10_movies),
+                    movies = uiState.top10movies.mapNotNull {
+                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())
+                    },
+                    onMovieClicked = onMovieClicked
                 )
-                LazyRow {
-                    items(uiState.top10movies) { movie ->
-                        MovieTile(
-                            movieId = movie.id.toString(),
-                            movieTitle = movie.title,
-                            movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating,
-                            onMovieClicked = {
-                                onMovieClicked(movie.id.toString())
-                            }
-                        )
-                    }
-                }
-
-                Text(
-                    text = stringResource(R.string.title_latest_movies),
-                    style = MaterialTheme.typography.headlineMedium,
-                    modifier = Modifier.padding(vertical = 16.dp)
+                Spacer(modifier = Modifier.height(16.dp))
+                MoviesList(
+                    listTitle = stringResource(R.string.title_latest_movies),
+                    movies = uiState.latestMovies.mapNotNull {
+                        Movie(it.id.toString(), it.imageUrl, it.title, it.rating?.toFloat())
+                    },
+                    onMovieClicked = onMovieClicked
                 )
-                LazyRow {
-                    items(uiState.latestMovies) { movie ->
-                        MovieTile(
-                            movieId = movie.id.toString(),
-                            movieTitle = movie.title,
-                            movieImageUrl = movie.imageUrl,
-                            movieRating = movie.rating,
-                            onMovieClicked = {
-                                onMovieClicked(movie.id.toString())
-                            }
-                        )
-                    }
-                }
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index c9cf11c9f..ebdc46ec7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.CircularProgressIndicator
@@ -26,7 +24,8 @@ import com.google.firebase.dataconnect.movies.GetUserByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.ui.components.Actor
 import com.google.firebase.example.dataconnect.ui.components.ActorsList
-import com.google.firebase.example.dataconnect.ui.components.MovieTile
+import com.google.firebase.example.dataconnect.ui.components.Movie
+import com.google.firebase.example.dataconnect.ui.components.MoviesList
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
 @Composable
@@ -105,10 +104,26 @@ fun ProfileScreen(
         }
         Spacer(modifier = Modifier.height(16.dp))
 
-        ProfileSection(title = "Watched Movies", content = { WatchedMoviesList(watchedMovies) })
+        MoviesList(
+            listTitle = stringResource(R.string.title_watched_movies),
+            movies = watchedMovies.mapNotNull {
+                Movie(it.movie.id.toString(), it.movie.imageUrl, it.movie.title, it.movie.rating?.toFloat())
+            },
+            onMovieClicked = {
+                // TODO
+            }
+        )
         Spacer(modifier = Modifier.height(16.dp))
 
-        ProfileSection(title = "Favorite Movies", content = { FavoriteMoviesList(favoriteMovies) })
+        MoviesList(
+            listTitle = stringResource(R.string.title_favorite_movies),
+            movies = favoriteMovies.mapNotNull {
+                Movie(it.movie.id.toString(), it.movie.imageUrl, it.movie.title, it.movie.rating?.toFloat())
+            },
+            onMovieClicked = {
+                // TODO
+            }
+        )
         Spacer(modifier = Modifier.height(16.dp))
 
         ActorsList(
@@ -120,6 +135,7 @@ fun ProfileScreen(
                 // TODO
             }
         )
+        Spacer(modifier = Modifier.height(16.dp))
 
         ProfileSection(title = "Reviews", content = { ReviewsList(name, reviews) })
         Spacer(modifier = Modifier.height(16.dp))
@@ -156,39 +172,3 @@ fun ReviewsList(
         }
     }
 }
-
-@Composable
-fun WatchedMoviesList(watchedItems: List<GetUserByIdQuery.Data.User.WatchedItem>) {
-    LazyRow {
-        items(watchedItems) { watchedItem ->
-            MovieTile(
-                movieId = watchedItem.movie.id.toString(),
-                movieImageUrl = watchedItem.movie.imageUrl,
-                movieTitle = watchedItem.movie.title,
-                movieRating = watchedItem.movie.rating,
-                tileWidth = 120.dp,
-                onMovieClicked = {
-                    // TODO
-                }
-            )
-        }
-    }
-}
-
-@Composable
-fun FavoriteMoviesList(favoriteItems: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem>) {
-    LazyRow {
-        items(favoriteItems) { favoriteItem ->
-            MovieTile(
-                movieId = favoriteItem.movie.id.toString(),
-                movieImageUrl = favoriteItem.movie.imageUrl,
-                movieTitle = favoriteItem.movie.title,
-                movieRating = favoriteItem.movie.rating,
-                tileWidth = 120.dp,
-                onMovieClicked = {
-                    // TODO
-                }
-            )
-        }
-    }
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
similarity index 53%
rename from dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
rename to dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
index 3939d3a92..37444b97f 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MovieTile.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
@@ -3,13 +3,10 @@ package com.google.firebase.example.dataconnect.ui.components
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.Card
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -21,40 +18,81 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 
+/**
+ * Used to represent a movie in a list UI
+ */
+data class Movie(
+    val id: String,
+    val imageUrl: String,
+    val title: String,
+    val rating: Float? = null
+)
+
+/**
+ * Displays a scrollable horizontal list of movies.
+ */
+@Composable
+fun MoviesList(
+    modifier: Modifier = Modifier,
+    listTitle: String,
+    movies: List<Movie>? = emptyList(),
+    onMovieClicked: (movieId: String) -> Unit
+) {
+    Column(
+        modifier = modifier.padding(horizontal = 16.dp)
+    ) {
+        Text(
+            text = listTitle,
+            style = MaterialTheme.typography.headlineMedium,
+            modifier = Modifier.padding(bottom = 8.dp)
+        )
+        LazyRow {
+            items(movies.orEmpty()) { movie ->
+                MovieTile(
+                    movie = movie,
+                    onMovieClicked = {
+                        onMovieClicked(movie.id.toString())
+                    }
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Used to display each movie item in the list.
+ */
 @Composable
 fun MovieTile(
     modifier: Modifier = Modifier,
     tileWidth: Dp = 150.dp,
-    movieId: String,
-    movieImageUrl: String,
-    movieTitle: String,
-    movieRating: Double? = null,
+    movie: Movie,
     onMovieClicked: (movieId: String) -> Unit
 ) {
     Card(
         modifier = modifier
-            .padding(vertical = 16.dp, horizontal = 4.dp)
+            .padding(4.dp)
             .sizeIn(maxWidth = tileWidth)
             .clickable {
-                onMovieClicked(movieId)
+                onMovieClicked(movie.id)
             },
     ) {
         AsyncImage(
-            model = movieImageUrl,
+            model = movie.imageUrl,
             contentDescription = null,
             contentScale = ContentScale.Crop,
             modifier = Modifier.aspectRatio(9f / 16f)
         )
         Text(
-            text = movieTitle,
+            text = movie.title,
             style = MaterialTheme.typography.titleMedium,
             modifier = Modifier.padding(8.dp),
             maxLines = 1,
             overflow = TextOverflow.Ellipsis
         )
-        movieRating?.let {
+        movie.rating?.let {
             Text(
-                text = "Rating: $movieRating",
+                text = "Rating: $it",
                 modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
                 style = MaterialTheme.typography.bodySmall
             )
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index b2e46db97..ca19a854b 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -31,6 +31,8 @@
 
     <!-- Actor Detail Screen -->
     <string name="biography_not_available">Biography not available</string>
+    <string name="title_main_roles">Main Roles</string>
+    <string name="title_supporting_roles">Supporting Roles</string>
 
     <!-- Profile Screen -->
     <string name="title_watched_movies">Watched Movies</string>

From b15d0ab70efdad67b09219a0ce88dc8ffd1bd2ac Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 03:19:05 +0100
Subject: [PATCH 36/63] refactor: make error and loading reusable components

---
 .../feature/actordetail/ActorDetailScreen.kt  | 16 +++------
 .../feature/genredetail/GenreDetailScreen.kt  | 23 ++++--------
 .../feature/moviedetail/MovieDetailScreen.kt  | 22 +++---------
 .../feature/movies/MoviesScreen.kt            | 21 +++--------
 .../feature/profile/ProfileScreen.kt          | 13 +++----
 .../dataconnect/ui/components/ActorsList.kt   |  2 ++
 .../dataconnect/ui/components/ErrorCard.kt    | 35 +++++++++++++++++++
 .../ui/components/LoadingScreen.kt            | 21 +++++++++++
 .../dataconnect/ui/components/MoviesList.kt   |  2 ++
 9 files changed, 84 insertions(+), 71 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/LoadingScreen.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index c6166ee1f..a379eaba8 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -34,7 +34,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
 import coil.compose.AsyncImage
 import com.google.firebase.dataconnect.movies.GetActorByIdQuery
 import com.google.firebase.example.dataconnect.R
-import com.google.firebase.example.dataconnect.feature.moviedetail.ErrorMessage
+import com.google.firebase.example.dataconnect.ui.components.ErrorCard
+import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
@@ -48,17 +49,10 @@ fun ActorDetailScreen(
     Scaffold { innerPadding ->
         when (uiState) {
             is ActorDetailUIState.Error -> {
-                ErrorMessage((uiState as ActorDetailUIState.Error).errorMessage)
+                ErrorCard((uiState as ActorDetailUIState.Error).errorMessage)
             }
 
-            ActorDetailUIState.Loading -> {
-                Box(
-                    contentAlignment = Alignment.Center,
-                    modifier = Modifier.fillMaxSize()
-                ) {
-                    CircularProgressIndicator()
-                }
-            }
+            ActorDetailUIState.Loading -> LoadingScreen()
 
             is ActorDetailUIState.Success -> {
                 val ui = uiState as ActorDetailUIState.Success
@@ -88,7 +82,7 @@ fun ActorInformation(
     onFavoriteToggled: (newValue: Boolean) -> Unit
 ) {
     if (actor == null) {
-        ErrorMessage(stringResource(R.string.error_movie_not_found))
+        ErrorCard(stringResource(R.string.error_movie_not_found))
     } else {
         Column(
             modifier = modifier
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 9af1540d7..24ea7a8f7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -1,23 +1,21 @@
 package com.google.firebase.example.dataconnect.feature.genredetail
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.ui.components.ErrorCard
+import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
@@ -36,29 +34,20 @@ fun GenreDetailScreen(
     uiState: GenreDetailUIState
 ) {
     when (uiState) {
-        GenreDetailUIState.Loading -> {
-            Box(
-                contentAlignment = Alignment.Center,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                CircularProgressIndicator()
-            }
-        }
+        GenreDetailUIState.Loading -> LoadingScreen()
 
-        is GenreDetailUIState.Error -> {
-            Text(uiState.errorMessage)
-        }
+        is GenreDetailUIState.Error -> ErrorCard(uiState.errorMessage)
 
         is GenreDetailUIState.Success -> {
             val scrollState = rememberScrollState()
             Column(
                 modifier = Modifier
-                    .padding(16.dp)
                     .verticalScroll(scrollState)
             ) {
                 Text(
                     text = stringResource(R.string.title_genre_detail, uiState.genreName),
-                    style = MaterialTheme.typography.headlineLarge
+                    style = MaterialTheme.typography.headlineLarge,
+                    modifier = Modifier.padding(8.dp)
                 )
                 MoviesList(
                     listTitle = stringResource(R.string.title_most_popular),
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 9ae2d55f0..e5e2a1f29 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -47,6 +47,8 @@ import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.ui.components.Actor
 import com.google.firebase.example.dataconnect.ui.components.ActorsList
+import com.google.firebase.example.dataconnect.ui.components.ErrorCard
+import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 
 @Composable
@@ -60,17 +62,10 @@ fun MovieDetailScreen(
     Scaffold { padding ->
         when (uiState) {
             is MovieDetailUIState.Error -> {
-                ErrorMessage((uiState as MovieDetailUIState.Error).errorMessage)
+                ErrorCard((uiState as MovieDetailUIState.Error).errorMessage)
             }
 
-            MovieDetailUIState.Loading -> {
-                Box(
-                    contentAlignment = Alignment.Center,
-                    modifier = Modifier.fillMaxSize()
-                ) {
-                    CircularProgressIndicator()
-                }
-            }
+            MovieDetailUIState.Loading -> LoadingScreen()
 
             is MovieDetailUIState.Success -> {
                 val ui = uiState as MovieDetailUIState.Success
@@ -130,7 +125,7 @@ fun MovieInformation(
     onFavoriteToggled: (newValue: Boolean) -> Unit
 ) {
     if (movie == null) {
-        ErrorMessage(stringResource(R.string.error_movie_not_found))
+        ErrorCard(stringResource(R.string.error_movie_not_found))
     } else {
         Column(
             modifier = modifier
@@ -282,10 +277,3 @@ fun UserReviews(
         }
     }
 }
-
-@Composable
-fun ErrorMessage(
-    message: String
-) {
-    Text(message)
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index ade279b1a..60a81b92b 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -1,24 +1,20 @@
 package com.google.firebase.example.dataconnect.feature.movies
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.ui.components.ErrorCard
+import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 
@@ -37,17 +33,8 @@ fun MoviesScreen(
     onMovieClicked: (movie: String) -> Unit
 ) {
     when (uiState) {
-        MoviesUIState.Loading -> {
-            Box(
-                contentAlignment = Alignment.Center,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                CircularProgressIndicator()
-            }
-        }
-        is MoviesUIState.Error -> {
-            Text(uiState.errorMessage)
-        }
+        MoviesUIState.Loading -> LoadingScreen()
+        is MoviesUIState.Error -> ErrorCard(uiState.errorMessage)
         is MoviesUIState.Success -> {
             val scrollState = rememberScrollState()
             Column(
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index ebdc46ec7..027c471af 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -24,6 +24,8 @@ import com.google.firebase.dataconnect.movies.GetUserByIdQuery
 import com.google.firebase.example.dataconnect.R
 import com.google.firebase.example.dataconnect.ui.components.Actor
 import com.google.firebase.example.dataconnect.ui.components.ActorsList
+import com.google.firebase.example.dataconnect.ui.components.ErrorCard
+import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
@@ -35,7 +37,7 @@ fun ProfileScreen(
     val uiState by profileViewModel.uiState.collectAsState()
     when (uiState) {
         is ProfileUIState.Error -> {
-            Text((uiState as ProfileUIState.Error).errorMessage)
+            ErrorCard((uiState as ProfileUIState.Error).errorMessage)
         }
 
         is ProfileUIState.AuthState -> {
@@ -63,14 +65,7 @@ fun ProfileScreen(
             )
         }
 
-        ProfileUIState.Loading -> {
-            Box(
-                contentAlignment = Alignment.Center,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                CircularProgressIndicator()
-            }
-        }
+        ProfileUIState.Loading -> LoadingScreen()
     }
 }
 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
index 6b181694d..55bd95156 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
@@ -3,6 +3,7 @@ package com.google.firebase.example.dataconnect.ui.components
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -44,6 +45,7 @@ fun ActorsList(
 ) {
     Column(
         modifier = modifier.padding(horizontal = 16.dp)
+            .fillMaxWidth()
     ) {
         Text(
             text = listTitle,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
new file mode 100644
index 000000000..08cb86dea
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
@@ -0,0 +1,35 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ErrorCard(
+    errorMessage: String
+) {
+    Card(
+        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
+        modifier = Modifier.padding(16.dp)
+            .fillMaxWidth()
+    ) {
+        Text(
+            text = errorMessage,
+            modifier = Modifier.padding(16.dp)
+                .fillMaxWidth()
+        )
+    }
+}
+
+@Composable
+@Preview
+fun ErrorCardPreview() {
+    ErrorCard("Something went terribly wrong :(")
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/LoadingScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/LoadingScreen.kt
new file mode 100644
index 000000000..df9a5a438
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/LoadingScreen.kt
@@ -0,0 +1,21 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+
+/**
+ * A screen that displays a loading spinner in the center.
+ */
+@Composable
+fun LoadingScreen() {
+    Box(
+        contentAlignment = Alignment.Center,
+        modifier = Modifier.fillMaxSize()
+    ) {
+        CircularProgressIndicator()
+    }
+}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
index 37444b97f..0be44d7ab 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt
@@ -3,6 +3,7 @@ package com.google.firebase.example.dataconnect.ui.components
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.lazy.LazyRow
@@ -40,6 +41,7 @@ fun MoviesList(
 ) {
     Column(
         modifier = modifier.padding(horizontal = 16.dp)
+            .fillMaxWidth()
     ) {
         Text(
             text = listTitle,

From 49a20a18afd17a9a02cd88696e21506529b9f6cf Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 03:35:19 +0100
Subject: [PATCH 37/63] refactor: make toggle button reusable

---
 .../feature/actordetail/ActorDetailScreen.kt  | 84 ++++++++-----------
 .../feature/moviedetail/MovieDetailScreen.kt  | 51 ++++-------
 .../dataconnect/ui/components/ToggleButton.kt | 36 ++++++++
 3 files changed, 86 insertions(+), 85 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ToggleButton.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index a379eaba8..1d30141b1 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -1,11 +1,9 @@
 package com.google.firebase.example.dataconnect.feature.actordetail
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
@@ -15,17 +13,12 @@ import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.outlined.FavoriteBorder
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.FilledTonalButton
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
@@ -38,6 +31,7 @@ import com.google.firebase.example.dataconnect.ui.components.ErrorCard
 import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
+import com.google.firebase.example.dataconnect.ui.components.ToggleButton
 
 @Composable
 fun ActorDetailScreen(
@@ -68,6 +62,25 @@ fun ActorDetailScreen(
                             actorDetailViewModel.toggleFavorite(it)
                         }
                     )
+                    MoviesList(
+                        listTitle = stringResource(R.string.title_main_roles),
+                        movies = ui.actor?.mainActors?.mapNotNull {
+                            Movie(it.id.toString(), it.imageUrl, it.title)
+                        },
+                        onMovieClicked = { movieId ->
+                            // TODO(thatfiredev): Support navigating to movie
+                        }
+                    )
+                    Spacer(modifier = Modifier.height(8.dp))
+                    MoviesList(
+                        listTitle = stringResource(R.string.title_supporting_actors),
+                        movies = ui.actor?.supportingActors?.mapNotNull {
+                            Movie(it.id.toString(), it.imageUrl, it.title)
+                        },
+                        onMovieClicked = { movieId ->
+                            // TODO(thatfiredev): Support navigating to movie
+                        }
+                    )
                 }
             }
         }
@@ -102,52 +115,21 @@ fun ActorInformation(
                         .aspectRatio(9f / 16f)
                         .padding(vertical = 8.dp)
                 )
-                Column(
-                    modifier = Modifier.padding(horizontal = 16.dp)
-                ) {
-                    Text(
-                        text = actor.biography ?: stringResource(R.string.biography_not_available),
-                        modifier = Modifier.fillMaxWidth()
-                    )
-                }
-            }
-            Spacer(modifier = Modifier.height(8.dp))
-            Row {
-                Spacer(modifier = Modifier.width(8.dp))
-                if (isActorFavorite) {
-                    FilledTonalButton(onClick = {
-                        onFavoriteToggled(false)
-                    }) {
-                        Icon(Icons.Filled.Favorite, "Favorite")
-                        Text("Favorite", modifier = Modifier.padding(start = 4.dp))
-                    }
-                } else {
-                    OutlinedButton(onClick = {
-                        onFavoriteToggled(true)
-                    }) {
-                        Icon(Icons.Outlined.FavoriteBorder, "Favorite")
-                        Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
-                    }
-                }
+                Text(
+                    text = actor.biography ?: stringResource(R.string.biography_not_available),
+                    modifier = Modifier
+                        .padding(horizontal = 16.dp)
+                        .fillMaxWidth()
+                )
             }
             Spacer(modifier = Modifier.height(8.dp))
-            MoviesList(
-                listTitle = stringResource(R.string.title_main_roles),
-                movies = actor.mainActors.mapNotNull {
-                    Movie(it.id.toString(), it.imageUrl, it.title)
-                },
-                onMovieClicked = { movieId ->
-                    // TODO(thatfiredev): Support navigating to movie
-                }
-            )
-            MoviesList(
-                listTitle = stringResource(R.string.title_supporting_actors),
-                movies = actor.supportingActors.mapNotNull {
-                    Movie(it.id.toString(), it.imageUrl, it.title)
-                },
-                onMovieClicked = { movieId ->
-                    // TODO(thatfiredev): Support navigating to movie
-                }
+            ToggleButton(
+                iconEnabled = Icons.Filled.Favorite,
+                iconDisabled = Icons.Outlined.FavoriteBorder,
+                textEnabled = stringResource(R.string.button_remove_favorite),
+                textDisabled = stringResource(R.string.button_favorite),
+                isEnabled = isActorFavorite,
+                onToggle = onFavoriteToggled
             )
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index e5e2a1f29..248ff07ae 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -1,6 +1,5 @@
 package com.google.firebase.example.dataconnect.feature.moviedetail
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -19,11 +18,8 @@ import androidx.compose.material.icons.outlined.Check
 import androidx.compose.material.icons.outlined.FavoriteBorder
 import androidx.compose.material.icons.outlined.Star
 import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.FilledTonalButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Slider
 import androidx.compose.material3.SuggestionChip
@@ -50,6 +46,7 @@ import com.google.firebase.example.dataconnect.ui.components.ActorsList
 import com.google.firebase.example.dataconnect.ui.components.ErrorCard
 import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
+import com.google.firebase.example.dataconnect.ui.components.ToggleButton
 
 @Composable
 fun MovieDetailScreen(
@@ -184,37 +181,23 @@ fun MovieInformation(
             }
             Spacer(modifier = Modifier.height(8.dp))
             Row {
-                if (isMovieWatched) {
-                    FilledTonalButton(onClick = {
-                        onWatchToggled(false)
-                    }) {
-                        Icon(Icons.Filled.CheckCircle, "Watched")
-                        Text("Watched", modifier = Modifier.padding(start = 4.dp))
-                    }
-                } else {
-                    OutlinedButton(onClick = {
-                        onWatchToggled(true)
-                    }) {
-                        Icon(Icons.Outlined.Check, "Watched")
-                        Text("Mark as watched", modifier = Modifier.padding(start = 4.dp))
-                    }
-                }
+                ToggleButton(
+                    iconEnabled = Icons.Filled.CheckCircle,
+                    iconDisabled = Icons.Outlined.Check,
+                    textEnabled = stringResource(R.string.button_unmark_watched),
+                    textDisabled = stringResource(R.string.button_mark_watched),
+                    isEnabled = isMovieWatched,
+                    onToggle = onWatchToggled
+                )
                 Spacer(modifier = Modifier.width(8.dp))
-                if (isMovieFavorite) {
-                    FilledTonalButton(onClick = {
-                        onFavoriteToggled(false)
-                    }) {
-                        Icon(Icons.Filled.Favorite, "Favorite")
-                        Text("Favorite", modifier = Modifier.padding(start = 4.dp))
-                    }
-                } else {
-                    OutlinedButton(onClick = {
-                        onFavoriteToggled(true)
-                    }) {
-                        Icon(Icons.Outlined.FavoriteBorder, "Favorite")
-                        Text("Add to Favorites", modifier = Modifier.padding(start = 4.dp))
-                    }
-                }
+                ToggleButton(
+                    iconEnabled = Icons.Filled.Favorite,
+                    iconDisabled = Icons.Outlined.FavoriteBorder,
+                    textEnabled = stringResource(R.string.button_remove_favorite),
+                    textDisabled = stringResource(R.string.button_favorite),
+                    isEnabled = isMovieFavorite,
+                    onToggle = onFavoriteToggled
+                )
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ToggleButton.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ToggleButton.kt
new file mode 100644
index 000000000..0d73abb8b
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ToggleButton.kt
@@ -0,0 +1,36 @@
+package com.google.firebase.example.dataconnect.ui.components
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ToggleButton(
+    iconEnabled: ImageVector,
+    iconDisabled: ImageVector,
+    textEnabled: String,
+    textDisabled: String,
+    isEnabled: Boolean,
+    onToggle: (newValue: Boolean) -> Unit
+) {
+    val onClick = {
+        onToggle(!isEnabled)
+    }
+    if (isEnabled) {
+        FilledTonalButton(onClick) {
+            Icon(iconEnabled, textEnabled)
+            Text(textEnabled, modifier = Modifier.padding(horizontal = 4.dp))
+        }
+    } else {
+        OutlinedButton(onClick) {
+            Icon(iconDisabled, textDisabled)
+            Text(textDisabled, modifier = Modifier.padding(horizontal = 4.dp))
+        }
+    }
+}

From 8546e348da4297511afe3e719cdb0c65a0821376 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 03:46:34 +0100
Subject: [PATCH 38/63] refactor: make actor details screen stateless

---
 .../feature/actordetail/ActorDetailScreen.kt  | 51 +++++++++++--------
 .../app/src/main/res/values/strings.xml       |  3 +-
 2 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index 1d30141b1..d2f46cc36 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -40,36 +40,48 @@ fun ActorDetailScreen(
 ) {
     actorDetailViewModel.setActorId(actorId)
     val uiState by actorDetailViewModel.uiState.collectAsState()
-    Scaffold { innerPadding ->
-        when (uiState) {
-            is ActorDetailUIState.Error -> {
-                ErrorCard((uiState as ActorDetailUIState.Error).errorMessage)
-            }
+    ActorDetailScreen(
+        uiState = uiState,
+        onMovieClicked = {
+            // TODO
+        },
+        onFavoriteToggled = {
+            actorDetailViewModel.toggleFavorite(it)
+        }
+    )
+}
+
+@Composable
+fun ActorDetailScreen(
+    uiState: ActorDetailUIState,
+    onMovieClicked: (actorId: String) -> Unit,
+    onFavoriteToggled: (newValue: Boolean) -> Unit
+) {
+    when (uiState) {
+        is ActorDetailUIState.Error -> ErrorCard(uiState.errorMessage)
 
-            ActorDetailUIState.Loading -> LoadingScreen()
+        ActorDetailUIState.Loading -> LoadingScreen()
 
-            is ActorDetailUIState.Success -> {
-                val ui = uiState as ActorDetailUIState.Success
+        is ActorDetailUIState.Success -> {
+            val ui = uiState
+            Scaffold { innerPadding ->
                 val scrollState = rememberScrollState()
                 Column(
-                    modifier = Modifier.verticalScroll(scrollState)
+                    modifier = Modifier
+                        .padding(innerPadding)
+                        .verticalScroll(scrollState)
                 ) {
                     ActorInformation(
-                        modifier = Modifier.padding(innerPadding),
                         actor = ui.actor,
                         isActorFavorite = ui.isFavorite,
-                        onFavoriteToggled = {
-                            actorDetailViewModel.toggleFavorite(it)
-                        }
+                        onFavoriteToggled = onFavoriteToggled
                     )
                     MoviesList(
                         listTitle = stringResource(R.string.title_main_roles),
                         movies = ui.actor?.mainActors?.mapNotNull {
                             Movie(it.id.toString(), it.imageUrl, it.title)
                         },
-                        onMovieClicked = { movieId ->
-                            // TODO(thatfiredev): Support navigating to movie
-                        }
+                        onMovieClicked = onMovieClicked
                     )
                     Spacer(modifier = Modifier.height(8.dp))
                     MoviesList(
@@ -77,12 +89,11 @@ fun ActorDetailScreen(
                         movies = ui.actor?.supportingActors?.mapNotNull {
                             Movie(it.id.toString(), it.imageUrl, it.title)
                         },
-                        onMovieClicked = { movieId ->
-                            // TODO(thatfiredev): Support navigating to movie
-                        }
+                        onMovieClicked = onMovieClicked
                     )
                 }
             }
+
         }
     }
 }
@@ -95,7 +106,7 @@ fun ActorInformation(
     onFavoriteToggled: (newValue: Boolean) -> Unit
 ) {
     if (actor == null) {
-        ErrorCard(stringResource(R.string.error_movie_not_found))
+        ErrorCard(stringResource(R.string.error_actor_not_found))
     } else {
         Column(
             modifier = modifier
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index ca19a854b..d58d70af7 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -17,7 +17,7 @@
     <string name="title_most_recent">Most Recent</string>
 
     <!-- Movie Detail Screen -->
-    <string name="error_movie_not_found">Couldnt find movie in the database</string>
+    <string name="error_movie_not_found">Couldn\'t find movie in the database</string>
     <string name="description_not_available">Description not available</string>
     <string name="button_mark_watched">Mark as watched</string>
     <string name="button_unmark_watched">Watched</string>
@@ -30,6 +30,7 @@
     <string name="button_submit_review">Submit Review</string>
 
     <!-- Actor Detail Screen -->
+    <string name="error_actor_not_found">Couldn\'t find actor in the database</string>
     <string name="biography_not_available">Biography not available</string>
     <string name="title_main_roles">Main Roles</string>
     <string name="title_supporting_roles">Supporting Roles</string>

From a45c5d4e3af3eec4989151a580f4dbc8db871bc8 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 03:54:21 +0100
Subject: [PATCH 39/63] refactor: move UserReviews into its own dedicated file

---
 .../feature/moviedetail/MovieDetailScreen.kt  | 64 +-------------
 .../feature/moviedetail/UserReviews.kt        | 84 +++++++++++++++++++
 .../feature/profile/ProfileScreen.kt          |  8 +-
 .../feature/profile/ProfileUIState.kt         |  8 +-
 .../feature/profile/ProfileViewModel.kt       |  8 +-
 5 files changed, 99 insertions(+), 73 deletions(-)
 create mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index 248ff07ae..d0b8729b9 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -88,7 +88,7 @@ fun MovieDetailScreen(
                         listTitle = stringResource(R.string.title_main_actors),
                         actors = movie?.mainActors?.mapNotNull {
                             Actor(it.id.toString(), it.name, it.imageUrl)
-                        }.orEmpty(),
+                        },
                         onActorClicked = { onActorClicked(it) }
                     )
                     // Supporting Actors list
@@ -96,14 +96,14 @@ fun MovieDetailScreen(
                         listTitle = stringResource(R.string.title_supporting_actors),
                         actors = movie?.supportingActors?.mapNotNull {
                             Actor(it.id.toString(), it.name, it.imageUrl)
-                        }.orEmpty(),
+                        },
                         onActorClicked = { onActorClicked(it) }
                     )
                     UserReviews(
                         onReviewSubmitted = { rating, text ->
                             movieDetailViewModel.addRating(rating, text)
                         },
-                        movie?.reviews ?: emptyList()
+                        movie?.reviews
                     )
                 }
 
@@ -202,61 +202,3 @@ fun MovieInformation(
         }
     }
 }
-
-@Composable
-fun UserReviews(
-    onReviewSubmitted: (rating: Float, text: String) -> Unit,
-    reviews: List<GetMovieByIdQuery.Data.Movie.ReviewsItem>
-) {
-    var reviewText by remember { mutableStateOf("") }
-    Text(
-        text = "User Reviews",
-        style = MaterialTheme.typography.headlineMedium,
-        modifier = Modifier.padding(horizontal = 16.dp)
-    )
-    Spacer(modifier = Modifier.height(8.dp))
-    Column(
-        modifier = Modifier
-            .fillMaxSize()
-            .padding(16.dp),
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-        var rating by remember { mutableFloatStateOf(3f) }
-        Text("Rating: ${rating}")
-        Slider(
-            value = rating,
-            // Round the value to the nearest 0.5
-            onValueChange = { rating = (Math.round(it * 2) / 2.0).toFloat() },
-            steps = 9,
-            valueRange = 1f..5f
-        )
-        TextField(
-            value = reviewText,
-            onValueChange = { reviewText = it },
-            label = { Text(stringResource(R.string.hint_write_review)) },
-            modifier = Modifier.fillMaxWidth()
-        )
-
-        Spacer(modifier = Modifier.height(16.dp))
-
-        Button(
-            onClick = {
-                onReviewSubmitted(rating, reviewText)
-                reviewText = ""
-            }
-        ) {
-            Text(stringResource(R.string.button_submit_review))
-        }
-    }
-    Column {
-        // TODO(thatfiredev): Handle cases where the list is too long to display
-        reviews.forEach {
-            ReviewCard(
-                userName = it.user.username,
-                date = it.reviewDate,
-                rating = it.rating?.toDouble() ?: 0.0,
-                text = it.reviewText ?: ""
-            )
-        }
-    }
-}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt
new file mode 100644
index 000000000..0b677d48d
--- /dev/null
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt
@@ -0,0 +1,84 @@
+package com.google.firebase.example.dataconnect.feature.moviedetail
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
+import com.google.firebase.example.dataconnect.R
+import com.google.firebase.example.dataconnect.ui.components.ReviewCard
+
+@Composable
+fun UserReviews(
+    onReviewSubmitted: (rating: Float, text: String) -> Unit,
+    reviews: List<GetMovieByIdQuery.Data.Movie.ReviewsItem>? = emptyList()
+) {
+    var reviewText by remember { mutableStateOf("") }
+    Text(
+        text = "User Reviews",
+        style = MaterialTheme.typography.headlineMedium,
+        modifier = Modifier.padding(horizontal = 16.dp)
+    )
+    Spacer(modifier = Modifier.height(8.dp))
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(16.dp),
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        var rating by remember { mutableFloatStateOf(3f) }
+        Text("Rating: ${rating}")
+        Slider(
+            value = rating,
+            // Round the value to the nearest 0.5
+            onValueChange = { rating = (Math.round(it * 2) / 2.0).toFloat() },
+            steps = 9,
+            valueRange = 1f..5f
+        )
+        TextField(
+            value = reviewText,
+            onValueChange = { reviewText = it },
+            label = { Text(stringResource(R.string.hint_write_review)) },
+            modifier = Modifier.fillMaxWidth()
+        )
+
+        Spacer(modifier = Modifier.height(16.dp))
+
+        Button(
+            onClick = {
+                onReviewSubmitted(rating, reviewText)
+                reviewText = ""
+            }
+        ) {
+            Text(stringResource(R.string.button_submit_review))
+        }
+    }
+    Column {
+        // TODO(thatfiredev): Handle cases where the list is too long to display
+        reviews.orEmpty().forEach {
+            ReviewCard(
+                userName = it.user.username,
+                date = it.reviewDate,
+                rating = it.rating?.toDouble() ?: 0.0,
+                text = it.reviewText ?: ""
+            )
+        }
+    }
+}
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 027c471af..7b7ee77d2 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -55,10 +55,10 @@ fun ProfileScreen(
             val ui = uiState as ProfileUIState.ProfileState
             ProfileScreen(
                 ui.username ?: "User",
-                ui.reviews,
-                ui.watchedMovies,
-                ui.favoriteMovies,
-                ui.favoriteActors,
+                ui.reviews.orEmpty(),
+                ui.watchedMovies.orEmpty(),
+                ui.favoriteMovies.orEmpty(),
+                ui.favoriteActors.orEmpty(),
                 onSignOut = {
                     profileViewModel.signOut()
                 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
index a86ce34b7..9977d7b77 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
@@ -11,9 +11,9 @@ sealed class ProfileUIState {
 
     data class ProfileState(
         val username: String?,
-        val reviews: List<GetUserByIdQuery.Data.User.ReviewsItem> = emptyList(),
-        val watchedMovies: List<GetUserByIdQuery.Data.User.WatchedItem> = emptyList(),
-        val favoriteMovies: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem> = emptyList(),
-        val favoriteActors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem> = emptyList()
+        val reviews: List<GetUserByIdQuery.Data.User.ReviewsItem>? = emptyList(),
+        val watchedMovies: List<GetUserByIdQuery.Data.User.WatchedItem>? = emptyList(),
+        val favoriteMovies: List<GetUserByIdQuery.Data.User.FavoriteMoviesItem>? = emptyList(),
+        val favoriteActors: List<GetUserByIdQuery.Data.User.FavoriteActorsItem>? = emptyList()
     ) : ProfileUIState()
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index 1fe8c75e4..9430d067e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -83,10 +83,10 @@ class ProfileViewModel(
                 val user = moviesConnector.getUserById.execute(id = userId).data.user
                 _uiState.value = ProfileUIState.ProfileState(
                     user?.username,
-                    favoriteMovies = user?.favoriteMovies ?: emptyList(),
-                    watchedMovies = user?.watched ?: emptyList(),
-                    favoriteActors = user?.favoriteActors ?: emptyList(),
-                    reviews = user?.reviews ?: emptyList()
+                    favoriteMovies = user?.favoriteMovies,
+                    watchedMovies = user?.watched,
+                    favoriteActors = user?.favoriteActors,
+                    reviews = user?.reviews
                 )
                 Log.d("DisplayUser", "$user")
             } catch (e: Exception) {

From f0f5ce487877e67622d38ab94705119e0384a5ba Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 04:06:08 +0100
Subject: [PATCH 40/63] refactor: turn actor tile into a card

---
 .../dataconnect/ui/components/ActorsList.kt   | 51 +++++++++++--------
 1 file changed, 30 insertions(+), 21 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
index 55bd95156..9e783df8d 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt
@@ -2,6 +2,7 @@ package com.google.firebase.example.dataconnect.ui.components
 
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Card
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -22,7 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import coil.compose.AsyncImage
 
-val ACTOR_CARD_SIZE = 80.dp
+val ACTOR_CARD_SIZE = 64.dp
 
 /**
  * Used to represent an actor in a list UI
@@ -68,31 +70,38 @@ fun ActorTile(
     actor: Actor,
     onActorClicked: (actorId: String) -> Unit
 ) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
+    Card(
         modifier = Modifier
-            .sizeIn(
-                maxWidth = ACTOR_CARD_SIZE,
-                maxHeight = ACTOR_CARD_SIZE + 32.dp
-            )
-            .padding(4.dp)
+            .padding(end = 8.dp)
             .clickable {
                 onActorClicked(actor.id)
             }
     ) {
-        AsyncImage(
-            model = actor.imageUrl,
-            contentDescription = null,
-            contentScale = ContentScale.Crop,
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
             modifier = Modifier
-                .size(ACTOR_CARD_SIZE)
-                .clip(CircleShape)
-        )
-        Text(
-            text = actor.name,
-            style = MaterialTheme.typography.bodyLarge,
-            maxLines = 1,
-            overflow = TextOverflow.Ellipsis
-        )
+                .sizeIn(
+                    maxWidth = 160.dp,
+                    maxHeight = ACTOR_CARD_SIZE + 16.dp
+                )
+                .padding(8.dp)
+                .fillMaxWidth()
+        ) {
+            AsyncImage(
+                model = actor.imageUrl,
+                contentDescription = null,
+                contentScale = ContentScale.Crop,
+                modifier = Modifier
+                    .padding(end = 8.dp)
+                    .size(ACTOR_CARD_SIZE)
+                    .clip(CircleShape)
+            )
+            Text(
+                text = actor.name,
+                style = MaterialTheme.typography.bodyLarge,
+                maxLines = 2,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
     }
 }

From bca483a2e6c55cf2cf9f50cd79f70aeed50fecca Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Fri, 20 Sep 2024 18:14:32 +0100
Subject: [PATCH 41/63] docs: update the getting started

---
 dataconnect/README.md | 77 ++++++++++++++++++++++++++++---------------
 1 file changed, 50 insertions(+), 27 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index d363b080a..76a1600fe 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -11,15 +11,33 @@ For more information about Firebase Data Connect visit [the docs](https://fireba
 Follow these steps to get up and running with Firebase Data Connect. For more detailed instructions,
 check out the [official documentation](https://firebase.google.com/docs/data-connect/quickstart).
 
-### 1. Create a New Data Connect Service and Cloud SQL Instance
-
-1. Open [Firebase Data Connect](https://console.firebase.google.com/u/0/project/_/dataconnect) in 
- your project in Firebase Console and select Get Started.
-2. Create a new Data Connect service and a Cloud SQL instance. Ensure the Blaze plan is active.
- Pricing details can be found at [Firebase Pricing](https://firebase.google.com/pricing).
-3. Select your server region, if you wish to use vector search, make sure to select `us-central1` region.
-4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance 
- can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
+### 1. Connect to your Firebase project
+
+1. If you haven't already, create a Firebase project.
+    1. In the [Firebase console](https://console.firebase.google.com), click
+        **Add project**, then follow the on-screen instructions.
+
+2. Upgrade your project to the Blaze plan. This lets you create a Cloud SQL
+    for PostgreSQL instance.
+
+    > Note: Though you set up billing in your Blaze upgrade, you won't be
+    charged for usage of {{data_connect_short}} or the
+    [default Cloud SQL for PostgreSQL configuration](https://firebase.google.com/docs/data-connect/#pricing)
+    during the preview.
+
+3. Navigate to the [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
+    of the Firebase console and follow the setup workflow:
+     - Select a location for your Cloud SQL for PostgreSQL database.
+     - Fill in the following fields:
+       - Service ID: `dataconnect`
+       - Cloud SQL Instance: `cloud-sql-instance`
+       - Database name: `postgres`
+4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance
+   can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
+
+5. Download and then add the Firebase Android configuration file (`google-services.json`) to your app:
+    1. Click **Download google-services.json** to obtain your Firebase Android config file.
+    2. Move your config file into the module (app-level) root directory of your app.
 
 ### 2. Set Up Firebase CLI
 
@@ -32,24 +50,29 @@ npm install -g firebase-tools
 ### 3. Cloning the repository
 This repository contains the quickstart to get started with the functionalities of Data Connect.
 
-1. Clone this repository to your local machine.
-1. (Private Preview only) Checkout the `fdc-quickstart` branch and open the project in Android Studio.
-1. Open the a terminal window and initialize your Firebase project with `firebase init dataconnect`.
-1. Overwrite only dataconnect.yaml when prompted, do not overwrite any other dataconnect files.
-   (Optional): If you intend on using other Firebase features, run `firebase init` instead, and select both DataConnect options as well as any feature you intend to use.
-1. Allow domains for Firebase Auth in your [project console](https://console.firebase.google.com/project/_/authentication/settings) (e.g. http://127.0.0.1).
-
-### 4. Running queries and mutations in VS Code
-The VSCode Firebase Extension allows you to generate Firebase Data Connect SDK code, run queries/mutations, and deploy Firebase Data Connect with a click. Alternatively, see below for CLI commands.
-
-1. Install [VS Code](https://code.visualstudio.com/).
-2. Download the [Firebase extension](https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/vsix%2Ffirebase-vscode-latest.vsix?alt=media) and [install](https://code.visualstudio.com/docs/editor/extension-marketplace#_install-an-extension) it.
-3. Open this quickstart in VS code, and in the left pane of the Firebase extension, and log in with your Firebase account.
-   (Optional): If your Firebase project was not initialized in the last section, you can click `Run firebase init` and select `Data Connect` to initialize.
-4. Click on deploy to deploy your schema to your cloud SQL instance. Or run `firebase deploy --only dataconnect` (this will also activate vectors search if it's enabled in the schema).
-5. Running the VSCode extension should automatically start the DataConnect emulators. If you see an emulators error, try running `firebase emulators:start dataconnect` manually.
-
-Now you should be able to deploy your schema, run mutations/queries, generate SDK code, and view your application locally.
+1. Clone this repository to your local machine:
+   ```sh
+   git clone https://github.com/firebase/quickstart-android.git
+   ```
+
+2. (Private Preview only) Checkout the `fdc-quickstart` branch (`git checkout fdc-quickstart`)
+   and open the project in Android Studio.
+
+### 4. Deploy the service to Firebase and generate SDKs
+
+1. Open the `quickstart-android/dataconnect/dataconnect` directory and deploy the schema with
+    the following command:
+    ```bash
+    firebase deploy
+    ```
+2. Once the deploy is complete, you should be able to see the movie schema in the
+ [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
+ of the Firebase console.
+
+3. Generate the Kotlin SDK by running:
+   ```bash
+   firebase dataconnect:sdk:generate
+   ```
 
 ### 5. Populating the database
 1. Run `1_movie_insert.gql`, `2_actor_insert.gql`, `3_movie_actor_insert.gql`, and `4_user_favorites_review_insert.gql` files in the `./dataconnect` directory in order using the VS code extension,

From a24417ea5bfc9886ad5d711a334ce0c6c4c10fc7 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 23 Sep 2024 15:09:56 +0100
Subject: [PATCH 42/63] docs: update README.md

---
 dataconnect/README.md                    | 8 ++++----
 dataconnect/dataconnect/dataconnect.yaml | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 76a1600fe..784d089c2 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -27,17 +27,17 @@ check out the [official documentation](https://firebase.google.com/docs/data-con
 
 3. Navigate to the [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
     of the Firebase console and follow the setup workflow:
-     - Select a location for your Cloud SQL for PostgreSQL database.
+     - Select a location for your Cloud SQL for PostgreSQL database (this sample uses `us-central1`). If you choose a different location, you'll also need to change the `quickstart-android/dataconnect/dataconnect/dataconnect.yaml` file. 
      - Fill in the following fields:
        - Service ID: `dataconnect`
-       - Cloud SQL Instance: `cloud-sql-instance`
-       - Database name: `postgres`
+       - Cloud SQL Instance: `fdc-sql`
+       - Database name: `fdcdb`
 4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance
    can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
 
 5. Download and then add the Firebase Android configuration file (`google-services.json`) to your app:
     1. Click **Download google-services.json** to obtain your Firebase Android config file.
-    2. Move your config file into the module (app-level) root directory of your app.
+    2. Move your config file into the `quickstart-android/dataconnect/app` directory.
 
 ### 2. Set Up Firebase CLI
 
diff --git a/dataconnect/dataconnect/dataconnect.yaml b/dataconnect/dataconnect/dataconnect.yaml
index a74d3b653..1eea139ad 100644
--- a/dataconnect/dataconnect/dataconnect.yaml
+++ b/dataconnect/dataconnect/dataconnect.yaml
@@ -5,7 +5,7 @@ schema:
   source: "./schema"
   datasource:
     postgresql:
-      database: "postgres"
+      database: "fdcdb"
       cloudSql:
-        instanceId: "cloud-sql-instance"
+        instanceId: "fdc-sql"
 connectorDirs: ["./connectors"]

From a8b2bf5aced6ab567cb7315d55b3ff6f6d59ac4c Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 23 Sep 2024 16:08:18 +0100
Subject: [PATCH 43/63] docs: update README.md

---
 dataconnect/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 784d089c2..8cc4359fd 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -21,7 +21,7 @@ check out the [official documentation](https://firebase.google.com/docs/data-con
     for PostgreSQL instance.
 
     > Note: Though you set up billing in your Blaze upgrade, you won't be
-    charged for usage of {{data_connect_short}} or the
+    charged for usage of Firebase Data Connect or the
     [default Cloud SQL for PostgreSQL configuration](https://firebase.google.com/docs/data-connect/#pricing)
     during the preview.
 

From c06b22c398d6fd933412229003cc4470d834ae44 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 14:27:42 +0100
Subject: [PATCH 44/63] add data_seed.gql and useEmulator()

---
 .../example/dataconnect/MainActivity.kt       |   4 +
 dataconnect/dataconnect/data_seed.gql         | 546 ++++++++++++++++++
 2 files changed, 550 insertions(+)
 create mode 100644 dataconnect/dataconnect/data_seed.gql

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index 6beab2c7e..c35bd0718 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -27,6 +27,8 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
+import com.google.firebase.dataconnect.movies.MoviesConnector
+import com.google.firebase.dataconnect.movies.instance
 import com.google.firebase.example.dataconnect.feature.actordetail.actorDetailScreen
 import com.google.firebase.example.dataconnect.feature.actordetail.navigateToActorDetail
 import com.google.firebase.example.dataconnect.feature.genredetail.genreDetailScreen
@@ -51,6 +53,8 @@ class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         enableEdgeToEdge()
+        // Comment the line below to use a production environment instead
+        MoviesConnector.instance.dataConnect.useEmulator("10.0.2.2", 9399)
         setContent {
             FirebaseDataConnectTheme {
                 val navController = rememberNavController()
diff --git a/dataconnect/dataconnect/data_seed.gql b/dataconnect/dataconnect/data_seed.gql
new file mode 100644
index 000000000..4e912b7e3
--- /dev/null
+++ b/dataconnect/dataconnect/data_seed.gql
@@ -0,0 +1,546 @@
+mutation {
+  # Insert movies
+  movie_insertMany(data: [
+    {
+      id: "550e8400-e29b-41d4-a716-446655440000",
+      title: "Inception",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Finception.jpg?alt=media&token=07b09781-b302-4623-a5c3-1956d0143168",
+      releaseYear: 2010,
+      genre: "sci-fi",
+      rating: 8.8,
+      description: "Dom Cobb (Leonardo DiCaprio) is a thief with the rare ability to enter people's dreams and steal their secrets from their subconscious. His skill has made him a valuable player in the world of corporate espionage but has also cost him everything he loves. Cobb gets a chance at redemption when he is offered a seemingly impossible task: plant an idea in someone's mind. If he succeeds, it will be the perfect crime, but a dangerous enemy anticipates Cobb's every move.",
+      tags: ["thriller", "action"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440001",
+      title: "The Matrix",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_matrix.jpg?alt=media&token=4975645d-fef8-409e-84a5-bcc1046e2059",
+      releaseYear: 1999,
+      genre: "action",
+      rating: 8.7,
+      description: "Thomas Anderson, a computer programmer, discovers that the world is actually a simulation controlled by malevolent machines in a dystopian future. Known as Neo, he joins a group of underground rebels led by Morpheus to fight the machines and free humanity. Along the way, Neo learns to manipulate the simulated reality, uncovering his true destiny.",
+      tags: ["sci-fi", "adventure"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440002",
+      title: "John Wick 4",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fjohn_wick_4.jpg?alt=media&token=463ed467-9daa-4281-965d-44e7cc4172d5",
+      releaseYear: 2023,
+      genre: "action",
+      rating: 8.1,
+      description: "John Wick (Keanu Reeves) uncovers a path to defeating The High Table, but before he can earn his freedom, he must face off against a new enemy with powerful alliances across the globe. The film follows Wick as he battles through various international locations, facing relentless adversaries and forming new alliances.",
+      tags: ["action", "thriller"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440003",
+      title: "The Dark Knight",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_dark_knight.jpg?alt=media&token=a9803c59-40d5-4758-a6f4-9a7c274a1218",
+      releaseYear: 2008,
+      genre: "action",
+      rating: 9.0,
+      description: "When the menace known as the Joker (Heath Ledger) emerges from his mysterious past, he wreaks havoc and chaos on the people of Gotham. The Dark Knight (Christian Bale) must accept one of the greatest psychological and physical tests of his ability to fight injustice.",
+      tags: ["action", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440004",
+      title: "Fight Club",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Ffight_club.jpg?alt=media&token=a4bc1933-2607-42cd-a860-e44c4587fd9c",
+      releaseYear: 1999,
+      genre: "drama",
+      rating: 8.8,
+      description: "An insomniac office worker (Edward Norton) and a devil-may-care soapmaker (Brad Pitt) form an underground fight club that evolves into something much more. The story explores themes of consumerism, masculinity, and the search for identity.",
+      tags: ["drama", "thriller"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440005",
+      title: "Pulp Fiction",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fpulp_fiction.jpg?alt=media&token=0df86e18-5cb1-45b3-a6d9-3f41563c3465",
+      releaseYear: 1994,
+      genre: "crime",
+      rating: 8.9,
+      description: "The lives of two mob hitmen, a boxer, a gangster and his wife, and a pair of diner bandits intertwine in four tales of violence and redemption. The film is known for its eclectic dialogue, ironic mix of humor and violence, and a host of cinematic allusions and pop culture references.",
+      tags: ["crime", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440006",
+      title: "The Lord of the Rings: The Fellowship of the Ring",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Flotr_fellowship.jpg?alt=media&token=92641d2d-6c52-4172-bd66-95fb86b4b96b",
+      releaseYear: 2001,
+      genre: "fantasy",
+      rating: 8.8,
+      description: "A meek Hobbit from the Shire, Frodo Baggins, and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron. The epic adventure begins the quest that will test their courage and bond.",
+      tags: ["fantasy", "adventure"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440007",
+      title: "The Shawshank Redemption",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_shawshanks_redemption.jpg?alt=media&token=f67b5ab2-a435-48b2-8251-5bf866b183e9",
+      releaseYear: 1994,
+      genre: "drama",
+      rating: 9.3,
+      description: "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency. The film follows Andy Dufresne (Tim Robbins), a banker sentenced to life in Shawshank State Penitentiary, and his friendship with Red (Morgan Freeman).",
+      tags: ["drama", "crime"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440008",
+      title: "Forrest Gump",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fforrest_gump.jpeg?alt=media&token=f21e88ce-6fab-4218-aa55-94738acc9b8f",
+      releaseYear: 1994,
+      genre: "drama",
+      rating: 8.8,
+      description: "The presidencies of Kennedy and Johnson, the events of Vietnam, Watergate, and other historical moments unfold from the perspective of an Alabama man with a low IQ. Forrest Gump (Tom Hanks) becomes an unwitting participant in many key moments of 20th-century U.S. history, all while maintaining his love for his childhood sweetheart Jenny (Robin Wright).",
+      tags: ["drama", "romance"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440009",
+      title: "The Godfather",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_godfather.jpg?alt=media&token=5297fd94-ae87-4995-9de5-3755232bad52",
+      releaseYear: 1972,
+      genre: "crime",
+      rating: 9.2,
+      description: "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son. The story follows the powerful Corleone family as they navigate power, loyalty, and betrayal.",
+      tags: ["crime", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440010",
+      title: "The Silence of the Lambs",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_silence_of_the_lambs.jpg?alt=media&token=7ca6abeb-b15c-4f5e-9280-5a590e89fe54",
+      releaseYear: 1991,
+      genre: "thriller",
+      rating: 8.6,
+      description: "A young F.B.I. cadet must receive the help of an incarcerated and manipulative cannibal killer to help catch another serial killer. Clarice Starling (Jodie Foster) seeks the assistance of Hannibal Lecter (Anthony Hopkins) to understand the mind of a killer.",
+      tags: ["thriller", "crime"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440011",
+      title: "Saving Private Ryan",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fsaving_private_ryan.jpg?alt=media&token=58ed877e-7ae0-4e30-9aee-d45c2deb7a00",
+      releaseYear: 1998,
+      genre: "war",
+      rating: 8.6,
+      description: "Following the Normandy Landings, a group of U.S. soldiers go behind enemy lines to retrieve a paratrooper whose brothers have been killed in action. The harrowing journey of Captain John H. Miller (Tom Hanks) and his men highlights the brutal reality of war.",
+      tags: ["war", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440012",
+      title: "The Avengers",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_avengers.jpg?alt=media&token=3d68ccad-2fa1-48da-a83e-7941e246c9f9",
+      releaseYear: 2012,
+      genre: "action",
+      rating: 8.0,
+      description: "Earth's mightiest heroes, including Iron Man, Captain America, Thor, Hulk, Black Widow, and Hawkeye, must come together to stop Loki and his alien army from enslaving humanity. Directed by Joss Whedon, the film is known for its witty dialogue, intense action sequences, and the chemistry among its ensemble cast.",
+      tags: ["action", "sci-fi"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440013",
+      title: "Gladiator",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fgladiator.jpg?alt=media&token=61d75825-b79f-4add-afdb-7da5eed53407",
+      releaseYear: 2000,
+      genre: "action",
+      rating: 8.5,
+      description: "A former Roman General, Maximus Decimus Meridius, seeks vengeance against the corrupt emperor Commodus who murdered his family and sent him into slavery. Directed by Ridley Scott, the film is known for its epic scale, intense battle scenes, and Russell Crowe's powerful performance.",
+      tags: ["action", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440014",
+      title: "Titanic",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Ftitanic.png?alt=media&token=dd03dc83-486e-4b03-9b03-2f9ed83fd9d0",
+      releaseYear: 1997,
+      genre: "romance",
+      rating: 7.8,
+      description: "A romantic drama recounting the ill-fated voyage of the R.M.S. Titanic through the eyes of Jack Dawson, a poor artist, and Rose DeWitt Bukater, a wealthy aristocrat. Their forbidden romance unfolds aboard the luxurious ship, which tragically sinks after striking an iceberg. Directed by James Cameron, the film is known for its epic scale, emotional depth, and stunning visuals.",
+      tags: ["romance", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440015",
+      title: "Avatar",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Favatar.jpg?alt=media&token=1c75b09d-7c7a-44bf-b7ad-e7da4d0b7193",
+      releaseYear: 2009,
+      genre: "sci-fi",
+      rating: 7.8,
+      description: "A paraplegic Marine named Jake Sully is sent on a unique mission to Pandora, an alien world, to bridge relations with the native Na'vi people. Torn between following his orders and protecting the world he feels is his home, Jake's journey becomes a battle for survival. Directed by James Cameron, 'Avatar' is renowned for its groundbreaking special effects and immersive 3D experience.",
+      tags: ["sci-fi", "adventure"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440016",
+      title: "Jurassic Park",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fjurassic_park.jpg?alt=media&token=1731ce71-3384-4435-8a5b-821d4fd286d3",
+      releaseYear: 1993,
+      genre: "adventure",
+      rating: 8.1,
+      description: "During a preview tour, a theme park suffers a major power breakdown that allows its cloned dinosaur exhibits to run amok. Directed by Steven Spielberg, 'Jurassic Park' is known for its groundbreaking special effects, thrilling storyline, and the suspenseful chaos unleashed by its prehistoric creatures.",
+      tags: ["adventure", "sci-fi"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440017",
+      title: "The Lion King",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fthe_lion_king.jpg?alt=media&token=3e4e4265-6ae7-47d6-a5ba-584de126ef00",
+      releaseYear: 1994,
+      genre: "animation",
+      rating: 8.5,
+      description: "A young lion prince, Simba, must overcome betrayal and tragedy to reclaim his rightful place as king. 'The Lion King' is a beloved animated musical known for its memorable songs, stunning animation, and a timeless tale of courage, loyalty, and redemption.",
+      tags: ["animation", "drama"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440018",
+      title: "Star Wars: Episode IV - A New Hope",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fstar_wars_4.jpg?alt=media&token=b4ea7e0c-707f-43dd-8633-9d962e77b5a4",
+      releaseYear: 1977,
+      genre: "sci-fi",
+      rating: 8.6,
+      description: "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee, and two droids to save the galaxy from the Empire's world-destroying battle station, the Death Star. Directed by George Lucas, 'A New Hope' revolutionized the sci-fi genre with its groundbreaking special effects and unforgettable characters.",
+      tags: ["sci-fi", "adventure"]
+    },
+    {
+      id: "550e8400-e29b-41d4-a716-446655440019",
+      title: "Blade Runner",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/movies%2Fblade_runner.jpg?alt=media&token=d8e94bdd-1477-49f3-b244-dd7a9c059fc1",
+      releaseYear: 1982,
+      genre: "sci-fi",
+      rating: 8.1,
+      description: "In a dystopian future, synthetic humans known as replicants are created by powerful corporations. A blade runner named Rick Deckard is tasked with hunting down and 'retiring' four replicants who have escaped to Earth. Directed by Ridley Scott, 'Blade Runner' is a seminal sci-fi thriller that explores themes of humanity and identity.",
+      tags: ["sci-fi", "thriller"]
+    }
+  ])
+
+  # Insert Actors
+  actor_insertMany(data: [
+    {
+      id: "123e4567-e89b-12d3-a456-426614174000",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fdicaprio.jpeg?alt=media&token=452e030a-efa5-4ef4-bb81-502b23241316",
+      name: "Leonardo DiCaprio"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174001",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fkeanu.jpg?alt=media&token=6056520c-ef3e-4823-aad0-108aab163115",
+      name: "Keanu Reeves"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174002",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fmcconaoghey.jpg?alt=media&token=d340ca18-ef51-44ac-a160-08e45b242cd7",
+      name: "Matthew McConaughey"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174003",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fbale.jpg?alt=media&token=666f1690-a550-458f-a1cf-9505b7d1af7d",
+      name: "Christian Bale"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174004",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fpitt.jpeg?alt=media&token=3a5283d4-f85c-4ba7-be72-51bc87ca4133",
+      name: "Brad Pitt"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174005",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fjackson.jpg?alt=media&token=07be0601-19fe-4b5d-b111-84fa71f32139",
+      name: "Samuel L. Jackson"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174006",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fmortensen.jpeg?alt=media&token=e3d1ec99-b8e7-42e9-9d1c-03f56f61ecf7",
+      name: "Viggo Mortensen"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174007",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Ffreeman.jpg?alt=media&token=94bc6227-119e-4ab0-b350-55fac7aeb062",
+      name: "Morgan Freeman"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174008",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fhanks.jpeg?alt=media&token=d92979ce-da62-4b28-afbe-b8740bbb9d32",
+      name: "Tom Hanks"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174009",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fpacino.jpg?alt=media&token=9c0c54b1-6913-48b5-8e5e-d6551dd2f182",
+      name: "Al Pacino"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174010",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Ffoster.jpg?alt=media&token=b429734c-0f2d-4840-b75b-6857eac7bb4f",
+      name: "Jodie Foster"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174011",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fcruise.jpg?alt=media&token=d34b0326-a8d1-4f01-86e5-f3f094594e5a",
+      name: "Tom Cruise"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174012",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fdowney.jpg?alt=media&token=dd291c96-6ef0-42fc-841c-902c80748b37",
+      name: "Robert Downey Jr."
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174013",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fcrowe.jpg?alt=media&token=46d413d5-ace8-404e-b018-8d7e6fe0d362",
+      name: "Russell Crowe"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174014",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fwinslet.jpg?alt=media&token=b675585e-356e-4361-a041-5ac1a6ee5922",
+      name: "Kate Winslet"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174015",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fweaver.jpeg?alt=media&token=263b5c3d-e0ee-43c3-854d-9b236c6df391",
+      name: "Sigourney Weaver"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174016",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fgoldblume.jpeg?alt=media&token=18277dd1-166c-4934-a02e-19ef141c86e2",
+      name: "Jeff Goldblum"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174017",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fjones.jpg?alt=media&token=f7ac9bc4-6e26-4b25-9a73-7a90f699424e",
+      name: "James Earl Jones"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174018",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fford.jpg?alt=media&token=928434c0-d492-4c8e-bdf0-0db585008d87",
+      name: "Harrison Ford"
+    },
+    {
+      id: "123e4567-e89b-12d3-a456-426614174019",
+      imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-quickstart-web.appspot.com/o/actors%2Fschwarzenegger.jpeg?alt=media&token=c46fb249-41ef-4084-b4ad-9517bee6ab46",
+      name: "Arnold Schwarzenegger"
+    }
+  ])
+  # Insert Movie Actor Joins
+  movieMetadata_insertMany(data: [
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440000",
+      director: "Christopher Nolan"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440001",
+      director: "Lana Wachowski, Lilly Wachowski"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440002",
+      director: "Chad Stahelski"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440003",
+      director: "Christopher Nolan"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440004",
+      director: "David Fincher"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440005",
+      director: "Quentin Tarantino"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440006",
+      director: "Peter Jackson"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440007",
+      director: "Frank Darabont"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440008",
+      director: "Robert Zemeckis"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440009",
+      director: "Francis Ford Coppola"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440010",
+      director: "Jonathan Demme"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440011",
+      director: "Steven Spielberg"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440012",
+      director: "Joss Whedon"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440013",
+      director: "Ridley Scott"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440014",
+      director: "James Cameron"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440015",
+      director: "James Cameron"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440016",
+      director: "Steven Spielberg"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440017",
+      director: "Roger Allers, Rob Minkoff"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440018",
+      director: "George Lucas"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440019",
+      director: "Ridley Scott"
+    }
+  ])
+
+  movieActor_insertMany(data: [
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440000",
+      actorId: "123e4567-e89b-12d3-a456-426614174000",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440001",
+      actorId: "123e4567-e89b-12d3-a456-426614174001",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440002",
+      actorId: "123e4567-e89b-12d3-a456-426614174001",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440003",
+      actorId: "123e4567-e89b-12d3-a456-426614174003",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440004",
+      actorId: "123e4567-e89b-12d3-a456-426614174004",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440005",
+      actorId: "123e4567-e89b-12d3-a456-426614174005",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440006",
+      actorId: "123e4567-e89b-12d3-a456-426614174006",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440007",
+      actorId: "123e4567-e89b-12d3-a456-426614174007",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440008",
+      actorId: "123e4567-e89b-12d3-a456-426614174008",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440009",
+      actorId: "123e4567-e89b-12d3-a456-426614174009",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440010",
+      actorId: "123e4567-e89b-12d3-a456-426614174010",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440011",
+      actorId: "123e4567-e89b-12d3-a456-426614174011",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440012",
+      actorId: "123e4567-e89b-12d3-a456-426614174012",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440013",
+      actorId: "123e4567-e89b-12d3-a456-426614174013",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440014",
+      actorId: "123e4567-e89b-12d3-a456-426614174014",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440015",
+      actorId: "123e4567-e89b-12d3-a456-426614174015",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440016",
+      actorId: "123e4567-e89b-12d3-a456-426614174016",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440017",
+      actorId: "123e4567-e89b-12d3-a456-426614174017",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440018",
+      actorId: "123e4567-e89b-12d3-a456-426614174018",
+      role: "main"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440019",
+      actorId: "123e4567-e89b-12d3-a456-426614174019",
+      role: "main"
+    },
+    # Supporting actors
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440000",
+      actorId: "123e4567-e89b-12d3-a456-426614174001",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440001",
+      actorId: "123e4567-e89b-12d3-a456-426614174002",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440002",
+      actorId: "123e4567-e89b-12d3-a456-426614174003",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440003",
+      actorId: "123e4567-e89b-12d3-a456-426614174004",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440004",
+      actorId: "123e4567-e89b-12d3-a456-426614174005",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440005",
+      actorId: "123e4567-e89b-12d3-a456-426614174006",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440006",
+      actorId: "123e4567-e89b-12d3-a456-426614174007",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440007",
+      actorId: "123e4567-e89b-12d3-a456-426614174008",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440008",
+      actorId: "123e4567-e89b-12d3-a456-426614174009",
+      role: "supporting"
+    },
+    {
+      movieId: "550e8400-e29b-41d4-a716-446655440009",
+      actorId: "123e4567-e89b-12d3-a456-426614174010",
+      role: "supporting"
+    }
+  ])
+}
\ No newline at end of file

From 2912f2951d2727579d72fc916d7819b8b01e8a32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= <rosariopf@google.com>
Date: Mon, 30 Sep 2024 19:06:43 +0100
Subject: [PATCH 45/63] Update README.md

Co-authored-by: Marina Coelho <marinacoelho@google.com>
---
 dataconnect/README.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 8cc4359fd..761f54539 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -26,16 +26,16 @@ check out the [official documentation](https://firebase.google.com/docs/data-con
     during the preview.
 
 3. Navigate to the [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
-    of the Firebase console and follow the setup workflow:
+    of the Firebase console, click on the "Get Started" button and follow the setup workflow:
      - Select a location for your Cloud SQL for PostgreSQL database (this sample uses `us-central1`). If you choose a different location, you'll also need to change the `quickstart-android/dataconnect/dataconnect/dataconnect.yaml` file. 
-     - Fill in the following fields:
+     - Select the option to create a new Cloud SQL instance and fill in the following fields:
        - Service ID: `dataconnect`
-       - Cloud SQL Instance: `fdc-sql`
+       - Cloud SQL Instance ID: `fdc-sql`
        - Database name: `fdcdb`
 4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance
    can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
 
-5. Download and then add the Firebase Android configuration file (`google-services.json`) to your app:
+5. If you haven’t already, add an Android app to your Firebase project, with the android package name `com.google.firebase.example.dataconnect`. Download and then add the Firebase Android configuration file (`google-services.json`) to your app:
     1. Click **Download google-services.json** to obtain your Firebase Android config file.
     2. Move your config file into the `quickstart-android/dataconnect/app` directory.
 

From 142dcb97347e64bfbf80f69f19032c89619ed1f2 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 17:59:24 +0100
Subject: [PATCH 46/63] refactor: use Navigation type safety

---
 dataconnect/app/build.gradle.kts              |   2 +-
 .../example/dataconnect/MainActivity.kt       | 149 +++++++++---------
 .../feature/actordetail/ActorDetailScreen.kt  |   7 +-
 .../actordetail/ActorDetailViewModel.kt       |  20 ++-
 .../feature/actordetail/Navigation.kt         |  26 ---
 .../feature/genredetail/GenreDetailScreen.kt  |   6 +-
 .../genredetail/GenreDetailViewModel.kt       |  15 +-
 .../feature/genredetail/Navigation.kt         |  27 ----
 .../feature/genres/GenresScreen.kt            |   3 +
 .../dataconnect/feature/genres/Navigation.kt  |  21 ---
 .../feature/moviedetail/MovieDetailScreen.kt  |   6 +-
 .../moviedetail/MovieDetailViewModel.kt       |  24 ++-
 .../feature/moviedetail/Navigation.kt         |  30 ----
 .../feature/movies/MoviesScreen.kt            |   4 +
 .../dataconnect/feature/movies/Navigation.kt  |  21 ---
 .../dataconnect/feature/profile/Navigation.kt |  21 ---
 .../feature/profile/ProfileScreen.kt          |   4 +
 .../feature/profile/ProfileViewModel.kt       |  15 +-
 gradle/libs.versions.toml                     |   6 +-
 19 files changed, 148 insertions(+), 259 deletions(-)
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
 delete mode 100644 dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index e6d0e400a..7484eb7c2 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -59,7 +59,7 @@ dependencies {
 
     implementation(libs.androidx.core.ktx)
     implementation(libs.androidx.lifecycle.runtime.ktx)
-    implementation(libs.androidx.lifecycle.viewmodel.android)
+    implementation(libs.androidx.lifecycle.viewmodel.compose)
     implementation(libs.androidx.activity.compose)
     implementation(platform(libs.androidx.compose.bom))
     implementation(libs.androidx.ui)
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
index c35bd0718..632b9ac78 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/MainActivity.kt
@@ -11,7 +11,6 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
 import androidx.compose.material.icons.filled.Menu
 import androidx.compose.material.icons.filled.Person
-import androidx.compose.material.icons.filled.Search
 import androidx.compose.material3.Icon
 import androidx.compose.material3.NavigationBar
 import androidx.compose.material3.NavigationBarItem
@@ -19,36 +18,39 @@ import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Text
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hasRoute
 import androidx.navigation.NavDestination.Companion.hierarchy
-import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.instance
-import com.google.firebase.example.dataconnect.feature.actordetail.actorDetailScreen
-import com.google.firebase.example.dataconnect.feature.actordetail.navigateToActorDetail
-import com.google.firebase.example.dataconnect.feature.genredetail.genreDetailScreen
-import com.google.firebase.example.dataconnect.feature.genredetail.navigateToGenreDetail
-import com.google.firebase.example.dataconnect.feature.genres.GENRES_ROUTE
-import com.google.firebase.example.dataconnect.feature.genres.genresScreen
-import com.google.firebase.example.dataconnect.feature.genres.navigateToGenres
-import com.google.firebase.example.dataconnect.feature.moviedetail.movieDetailScreen
-import com.google.firebase.example.dataconnect.feature.moviedetail.navigateToMovieDetail
-import com.google.firebase.example.dataconnect.feature.movies.MOVIES_ROUTE
-import com.google.firebase.example.dataconnect.feature.movies.moviesScreen
-import com.google.firebase.example.dataconnect.feature.movies.navigateToMovies
-import com.google.firebase.example.dataconnect.feature.profile.PROFILE_ROUTE
-import com.google.firebase.example.dataconnect.feature.profile.navigateToProfile
-import com.google.firebase.example.dataconnect.feature.profile.profileScreen
-import com.google.firebase.example.dataconnect.feature.search.SEARCH_ROUTE
-import com.google.firebase.example.dataconnect.feature.search.navigateToSearch
+import com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailRoute
+import com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailScreen
+import com.google.firebase.example.dataconnect.feature.genredetail.GenreDetailRoute
+import com.google.firebase.example.dataconnect.feature.genredetail.GenreDetailScreen
+import com.google.firebase.example.dataconnect.feature.genres.GenresRoute
+import com.google.firebase.example.dataconnect.feature.genres.GenresScreen
+import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailRoute
+import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailScreen
+import com.google.firebase.example.dataconnect.feature.movies.MoviesRoute
+import com.google.firebase.example.dataconnect.feature.movies.MoviesScreen
+import com.google.firebase.example.dataconnect.feature.profile.ProfileRoute
+import com.google.firebase.example.dataconnect.feature.profile.ProfileScreen
 import com.google.firebase.example.dataconnect.feature.search.searchScreen
 import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
 
+data class TopLevelRoute<T : Any>(val labelResId: Int, val route: T, val icon: ImageVector)
+
+val TOP_LEVEL_ROUTES = listOf(
+    TopLevelRoute(R.string.label_movies, MoviesRoute, Icons.Filled.Home),
+    TopLevelRoute(R.string.label_genres, GenresRoute, Icons.Filled.Menu),
+    TopLevelRoute(R.string.label_profile, ProfileRoute, Icons.Filled.Person)
+)
+
 class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -64,75 +66,70 @@ class MainActivity : ComponentActivity() {
                         NavigationBar {
                             val navBackStackEntry by navController.currentBackStackEntryAsState()
                             val currentDestination = navBackStackEntry?.destination
-                            NavigationBarItem(
-                                icon = { Icon(Icons.Filled.Home, contentDescription = null) },
-                                label = { Text(stringResource(R.string.label_movies)) },
-                                selected = isRouteSelected(currentDestination, MOVIES_ROUTE),
-                                onClick = {
-                                    navController.navigateToMovies { launchSingleTop = true }
-                                }
-                            )
-                            NavigationBarItem(
-                                icon = { Icon(Icons.Filled.Menu, contentDescription = null) },
-                                label = { Text(stringResource(R.string.label_genres)) },
-                                selected = isRouteSelected(currentDestination, GENRES_ROUTE),
-                                onClick = {
-                                    navController.navigateToGenres { launchSingleTop = true }
-                                }
-                            )
-                            NavigationBarItem(
-                                icon = { Icon(Icons.Filled.Search, contentDescription = null) },
-                                label = { Text(stringResource(R.string.label_search)) },
-                                selected = isRouteSelected(currentDestination, SEARCH_ROUTE),
-                                onClick = {
-                                    navController.navigateToSearch { launchSingleTop = true }
-                                }
-                            )
-                            NavigationBarItem(
-                                icon = { Icon(Icons.Filled.Person, contentDescription = null) },
-                                label = { Text(stringResource(R.string.label_profile)) },
-                                selected = isRouteSelected(currentDestination, PROFILE_ROUTE),
-                                onClick = {
-                                    navController.navigateToProfile { launchSingleTop = true }
-                                }
-                            )
+
+                            TOP_LEVEL_ROUTES.forEach { topLevelRoute ->
+                                val label = stringResource(topLevelRoute.labelResId)
+                                NavigationBarItem(
+                                    icon = { Icon(topLevelRoute.icon, contentDescription = label) },
+                                    label = { Text(label) },
+                                    selected = currentDestination?.hierarchy?.any {
+                                        it.hasRoute(topLevelRoute.route::class)
+                                    } == true,
+                                    onClick = {
+                                        navController.navigate(
+                                            topLevelRoute.route,
+                                            { launchSingleTop = true }
+                                        )
+                                    }
+                                )
+                            }
                         }
                     }
                 ) { innerPadding ->
                     NavHost(
                         navController,
-                        startDestination = MOVIES_ROUTE,
+                        startDestination = MoviesRoute,
                         Modifier
                             .padding(innerPadding)
                             .consumeWindowInsets(innerPadding),
                     ) {
-                        moviesScreen(onMovieClicked = { movieId ->
-                            navController.navigateToMovieDetail(movieId) {
-                                launchSingleTop = true
-                            }
-                        })
-                        movieDetailScreen(
-                            onActorClicked = { actorId ->
-                                navController.navigateToActorDetail(actorId) {
-                                    launchSingleTop = true
+                        composable<MoviesRoute>() {
+                            MoviesScreen(
+                                onMovieClicked = { movieId ->
+                                    navController.navigate(
+                                        route = MovieDetailRoute(movieId),
+                                        builder = {
+                                            launchSingleTop = true
+                                        }
+                                    )
                                 }
-                            }
-                        )
-                        actorDetailScreen()
-                        genresScreen(onGenreClicked = { genre ->
-                            navController.navigateToGenreDetail(genre) {
-                                launchSingleTop = true
-                            }
-                        })
-                        genreDetailScreen()
+                            )
+                        }
+                        composable<MovieDetailRoute> {
+                            MovieDetailScreen(
+                                onActorClicked = { actorId ->
+                                    navController.navigate(
+                                        ActorDetailRoute(actorId),
+                                        { launchSingleTop = true }
+                                    )
+                                }
+                            )
+                        }
+                        composable<ActorDetailRoute>() { ActorDetailScreen() }
+                        composable<GenresRoute> {
+                            GenresScreen(onGenreClicked = { genre ->
+                                navController.navigate(
+                                    GenreDetailRoute(genre),
+                                    { launchSingleTop = true }
+                                )
+                            })
+                        }
+                        composable<GenreDetailRoute> { GenreDetailScreen() }
                         searchScreen()
-                        profileScreen()
+                        composable<ProfileRoute> { ProfileScreen() }
                     }
                 }
             }
         }
     }
 }
-
-private fun isRouteSelected(currentDestination: NavDestination?, route: String) =
-    currentDestination?.hierarchy?.any { it.route?.startsWith(route) ?: false } == true
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index d2f46cc36..58f770dd7 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -32,13 +32,16 @@ import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 import com.google.firebase.example.dataconnect.ui.components.ToggleButton
+import kotlinx.serialization.Serializable
+
+
+@Serializable
+data class ActorDetailRoute(val actorId: String)
 
 @Composable
 fun ActorDetailScreen(
-    actorId: String,
     actorDetailViewModel: ActorDetailViewModel = viewModel()
 ) {
-    actorDetailViewModel.setActorId(actorId)
     val uiState by actorDetailViewModel.uiState.collectAsState()
     ActorDetailScreen(
         uiState = uiState,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
index ae6877a8b..6cadc7571 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
@@ -1,7 +1,9 @@
 package com.google.firebase.example.dataconnect.feature.actordetail
 
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import androidx.navigation.toRoute
 import com.google.firebase.Firebase
 import com.google.firebase.auth.FirebaseAuth
 import com.google.firebase.auth.auth
@@ -14,17 +16,23 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class ActorDetailViewModel(
-    private val firebaseAuth: FirebaseAuth = Firebase.auth,
-    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+    savedStateHandle: SavedStateHandle
 ) : ViewModel() {
-    private var actorId: String = ""
+    private val actorDetailRoute = savedStateHandle.toRoute<ActorDetailRoute>()
+    private val actorId: String = actorDetailRoute.actorId
+
+    private val firebaseAuth: FirebaseAuth = Firebase.auth
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 
     private val _uiState = MutableStateFlow<ActorDetailUIState>(ActorDetailUIState.Loading)
     val uiState: StateFlow<ActorDetailUIState>
         get() = _uiState
 
-    fun setActorId(id: String) {
-        actorId = id
+    init {
+        fetchActor()
+    }
+
+    private fun fetchActor() {
         viewModelScope.launch {
             try {
                 val user = firebaseAuth.currentUser
@@ -64,7 +72,7 @@ class ActorDetailViewModel(
                     )
                 }
                 // Re-run the query to fetch the actor details
-                setActorId(actorId)
+                fetchActor()
             } catch (e: Exception) {
                 _uiState.value = ActorDetailUIState.Error(e.message ?: "")
             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt
deleted file mode 100644
index 152c75971..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/Navigation.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.actordetail
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-
-const val ACTOR_DETAIL_ROUTE = "actors/{actor}"
-
-fun NavController.navigateToActorDetail(
-    actorId: String,
-    navOptions: NavOptionsBuilder.() -> Unit = { }
-) = navigate(ACTOR_DETAIL_ROUTE.replace("{actor}", actorId), navOptions)
-
-fun NavGraphBuilder.actorDetailScreen() {
-    composable(
-        route = ACTOR_DETAIL_ROUTE
-    ) { navBackStackEntry ->
-        navBackStackEntry.arguments?.let {
-            val actorId = it.getString("actor")
-            actorId?.let { id ->
-                ActorDetailScreen(id)
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index 24ea7a8f7..c7e9e0e4b 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -18,13 +18,15 @@ import com.google.firebase.example.dataconnect.ui.components.ErrorCard
 import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GenreDetailRoute(val genre: String)
 
 @Composable
 fun GenreDetailScreen(
-    genre: String,
     moviesViewModel: GenreDetailViewModel = viewModel()
 ) {
-    moviesViewModel.setGenre(genre)
     val movies by moviesViewModel.uiState.collectAsState()
     GenreDetailScreen(movies)
 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
index 811250a78..ec507e6c5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
@@ -1,7 +1,9 @@
 package com.google.firebase.example.dataconnect.feature.genredetail
 
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import androidx.navigation.toRoute
 import com.google.firebase.dataconnect.movies.MoviesConnector
 import com.google.firebase.dataconnect.movies.execute
 import com.google.firebase.dataconnect.movies.instance
@@ -10,17 +12,20 @@ import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class GenreDetailViewModel(
-    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+    savedStateHandle: SavedStateHandle
 ) : ViewModel() {
-    private var genre = ""
+    private val genre = savedStateHandle.toRoute<GenreDetailRoute>().genre
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 
     private val _uiState = MutableStateFlow<GenreDetailUIState>(GenreDetailUIState.Loading)
     val uiState: StateFlow<GenreDetailUIState>
         get() = _uiState
 
-    // TODO(thatfiredev): Create a ViewModelFactory to set genre
-    fun setGenre(genre: String) {
-        this.genre = genre
+    init {
+        fetchGenre()
+    }
+
+    private fun fetchGenre() {
         viewModelScope.launch {
             try {
                 val data = moviesConnector.listMoviesByGenre.execute(genre.lowercase()).data
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt
deleted file mode 100644
index 45edf83b3..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/Navigation.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.genredetail
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptions
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-import androidx.navigation.navArgument
-
-const val GENRE_DETAIL_ROUTE = "genres/{genre}"
-
-fun NavController.navigateToGenreDetail(
-    genre: String,
-    navOptions: NavOptionsBuilder.() -> Unit = { }
-) = navigate(GENRE_DETAIL_ROUTE.replace("{genre}", genre), navOptions)
-
-fun NavGraphBuilder.genreDetailScreen() {
-    composable(
-        route = GENRE_DETAIL_ROUTE
-    ) { backStackEntry ->
-        backStackEntry.arguments?.let {
-            GenreDetailScreen(it.getString("genre", "Action"))
-        }
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
index 4bcd6bf40..efa98a37b 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/GenresScreen.kt
@@ -11,7 +11,10 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import kotlinx.serialization.Serializable
 
+@Serializable
+object GenresRoute
 
 @Composable
 fun GenresScreen(
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
deleted file mode 100644
index f2fce931a..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genres/Navigation.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.genres
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-
-const val GENRES_ROUTE = "genres"
-
-fun NavController.navigateToGenres(navOptions: NavOptionsBuilder.() -> Unit) =
-    navigate(GENRES_ROUTE, navOptions)
-
-fun NavGraphBuilder.genresScreen(
-    onGenreClicked: (genre: String) -> Unit
-) {
-    composable(route = GENRES_ROUTE) {
-        GenresScreen(onGenreClicked)
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
index d0b8729b9..9ca23be69 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt
@@ -47,14 +47,16 @@ import com.google.firebase.example.dataconnect.ui.components.ErrorCard
 import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
 import com.google.firebase.example.dataconnect.ui.components.ToggleButton
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MovieDetailRoute(val movieId: String)
 
 @Composable
 fun MovieDetailScreen(
-    movieId: String,
     onActorClicked: (actorId: String) -> Unit,
     movieDetailViewModel: MovieDetailViewModel = viewModel()
 ) {
-    movieDetailViewModel.setMovieId(movieId)
     val uiState by movieDetailViewModel.uiState.collectAsState()
     Scaffold { padding ->
         when (uiState) {
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index 929c2225e..1212c7761 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -1,7 +1,9 @@
 package com.google.firebase.example.dataconnect.feature.moviedetail
 
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import androidx.navigation.toRoute
 import com.google.firebase.Firebase
 import com.google.firebase.auth.FirebaseAuth
 import com.google.firebase.auth.auth
@@ -16,17 +18,23 @@ import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 class MovieDetailViewModel(
-    private val firebaseAuth: FirebaseAuth = Firebase.auth,
-    private val moviesConnector: MoviesConnector = MoviesConnector.instance
+    savedStateHandle: SavedStateHandle
 ) : ViewModel() {
-    private var movieId: String = ""
+    private val movieDetailRoute = savedStateHandle.toRoute<MovieDetailRoute>()
+    private val movieId: String = movieDetailRoute.movieId
+
+    private val firebaseAuth: FirebaseAuth = Firebase.auth
+    private val moviesConnector: MoviesConnector = MoviesConnector.instance
 
     private val _uiState = MutableStateFlow<MovieDetailUIState>(MovieDetailUIState.Loading)
     val uiState: StateFlow<MovieDetailUIState>
         get() = _uiState
 
-    fun setMovieId(id: String) {
-        movieId = id
+    init {
+        fetchMovie()
+    }
+
+    private fun fetchMovie() {
         viewModelScope.launch {
             try {
                 val user = firebaseAuth.currentUser
@@ -74,7 +82,7 @@ class MovieDetailViewModel(
                     )
                 }
                 // Re-run the query to fetch movie
-                setMovieId(movieId)
+                fetchMovie()
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
@@ -95,7 +103,7 @@ class MovieDetailViewModel(
                     )
                 }
                 // Re-run the query to fetch movie
-                setMovieId(movieId)
+                fetchMovie()
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
@@ -114,7 +122,7 @@ class MovieDetailViewModel(
                 )
                 // TODO(thatfiredev): should we have a way of only refetching the reviews?
                 // Re-run the query to fetch movie
-                setMovieId(movieId)
+                fetchMovie()
             } catch (e: Exception) {
                 _uiState.value = MovieDetailUIState.Error(e.message ?: "")
             }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
deleted file mode 100644
index ecef5023d..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/Navigation.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.moviedetail
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-
-const val MOVIE_DETAIL_ROUTE = "movies/{movie}"
-
-fun NavController.navigateToMovieDetail(
-    movieId: String,
-    navOptions: NavOptionsBuilder.() -> Unit = { }
-) = navigate(MOVIE_DETAIL_ROUTE.replace("{movie}", movieId), navOptions)
-
-fun NavGraphBuilder.movieDetailScreen(
-    onActorClicked: (actorId: String) -> Unit
-) {
-    composable(
-        route = MOVIE_DETAIL_ROUTE
-    ) { backStackEntry ->
-        backStackEntry.arguments?.let {
-            val movieId = it.getString("movie")
-            movieId?.let { id ->
-                MovieDetailScreen(id, onActorClicked)
-            }
-        }
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
index 60a81b92b..c76090264 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesScreen.kt
@@ -17,6 +17,10 @@ import com.google.firebase.example.dataconnect.ui.components.ErrorCard
 import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
+import kotlinx.serialization.Serializable
+
+@Serializable
+object MoviesRoute
 
 @Composable
 fun MoviesScreen(
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
deleted file mode 100644
index 06c256d04..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/Navigation.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.movies
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-
-const val MOVIES_ROUTE = "movies"
-
-fun NavController.navigateToMovies(navOptions: NavOptionsBuilder.() -> Unit) =
-    navigate(MOVIES_ROUTE, navOptions)
-
-fun NavGraphBuilder.moviesScreen(
-    onMovieClicked: (movie: String) -> Unit
-) {
-    composable(route = MOVIES_ROUTE) {
-        MoviesScreen(onMovieClicked)
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
deleted file mode 100644
index f6ce7a7f8..000000000
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/Navigation.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.google.firebase.example.dataconnect.feature.profile
-
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.compose.composable
-
-const val PROFILE_ROUTE = "profile_route"
-
-fun NavController.navigateToProfile(navOptions: NavOptionsBuilder.() -> Unit) =
-    navigate(PROFILE_ROUTE, navOptions)
-
-fun NavGraphBuilder.profileScreen(
-
-) {
-    composable(route = PROFILE_ROUTE) {
-        ProfileScreen()
-    }
-}
-
-
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
index 7b7ee77d2..c020552b6 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileScreen.kt
@@ -29,6 +29,10 @@ import com.google.firebase.example.dataconnect.ui.components.LoadingScreen
 import com.google.firebase.example.dataconnect.ui.components.Movie
 import com.google.firebase.example.dataconnect.ui.components.MoviesList
 import com.google.firebase.example.dataconnect.ui.components.ReviewCard
+import kotlinx.serialization.Serializable
+
+@Serializable
+object ProfileRoute
 
 @Composable
 fun ProfileScreen(
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index 9430d067e..1620a2959 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -1,6 +1,7 @@
 package com.google.firebase.example.dataconnect.feature.profile
 
 import android.util.Log
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.google.firebase.Firebase
@@ -27,14 +28,12 @@ class ProfileViewModel(
     private val authStateListener: AuthStateListener
 
     init {
-        authStateListener = object : AuthStateListener {
-            override fun onAuthStateChanged(auth: FirebaseAuth) {
-                val currentUser = auth.currentUser
-                if (currentUser != null) {
-                    displayUser(currentUser.uid)
-                } else {
-                    _uiState.value = ProfileUIState.AuthState
-                }
+        authStateListener = AuthStateListener {
+            val currentUser = auth.currentUser
+            if (currentUser != null) {
+                displayUser(currentUser.uid)
+            } else {
+                _uiState.value = ProfileUIState.AuthState
             }
         }
         auth.addAuthStateListener(authStateListener)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d1ea661b2..b4330e046 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
 agp = "8.6.0"
 coilCompose = "2.6.0"
 firebaseAuth = "23.0.0"
-firebaseDataConnect = "16.0.0-alpha03"
+firebaseDataConnect = "16.0.0-alpha05"
 kotlin = "2.0.20"
 coreKtx = "1.13.1"
 junit = "4.13.2"
@@ -12,12 +12,12 @@ lifecycle = "2.8.1"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
 googleServices = "4.4.2"
-composeNavigation = "2.7.7"
+composeNavigation = "2.8.1"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
 androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycle" }
-androidx-lifecycle-viewmodel-android = { module = "androidx.lifecycle:lifecycle-viewmodel-android", version.ref = "lifecycle" }
+androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
 coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
 firebase-auth = { module = "com.google.firebase:firebase-auth", version.ref = "firebaseAuth" }
 firebase-dataconnect = { module = "com.google.firebase:firebase-dataconnect", version.ref = "firebaseDataConnect" }

From 3e0b914a8f5320059b0133e986137a02bc825cd4 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 18:37:00 +0100
Subject: [PATCH 47/63] refactor: delete extra uiState

---
 .../feature/actordetail/ActorDetailScreen.kt          | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
index 58f770dd7..0441dbdc6 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt
@@ -63,10 +63,9 @@ fun ActorDetailScreen(
     when (uiState) {
         is ActorDetailUIState.Error -> ErrorCard(uiState.errorMessage)
 
-        ActorDetailUIState.Loading -> LoadingScreen()
+        is ActorDetailUIState.Loading -> LoadingScreen()
 
         is ActorDetailUIState.Success -> {
-            val ui = uiState
             Scaffold { innerPadding ->
                 val scrollState = rememberScrollState()
                 Column(
@@ -75,13 +74,13 @@ fun ActorDetailScreen(
                         .verticalScroll(scrollState)
                 ) {
                     ActorInformation(
-                        actor = ui.actor,
-                        isActorFavorite = ui.isFavorite,
+                        actor = uiState.actor,
+                        isActorFavorite = uiState.isFavorite,
                         onFavoriteToggled = onFavoriteToggled
                     )
                     MoviesList(
                         listTitle = stringResource(R.string.title_main_roles),
-                        movies = ui.actor?.mainActors?.mapNotNull {
+                        movies = uiState.actor?.mainActors?.mapNotNull {
                             Movie(it.id.toString(), it.imageUrl, it.title)
                         },
                         onMovieClicked = onMovieClicked
@@ -89,7 +88,7 @@ fun ActorDetailScreen(
                     Spacer(modifier = Modifier.height(8.dp))
                     MoviesList(
                         listTitle = stringResource(R.string.title_supporting_actors),
-                        movies = ui.actor?.supportingActors?.mapNotNull {
+                        movies = uiState.actor?.supportingActors?.mapNotNull {
                             Movie(it.id.toString(), it.imageUrl, it.title)
                         },
                         onMovieClicked = onMovieClicked

From 496af823ea1de9a2ba9afd65fa81485479e78d57 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 18:40:22 +0100
Subject: [PATCH 48/63] refactor favoriteActor for readability

---
 .../dataconnect/feature/actordetail/ActorDetailViewModel.kt | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
index 6cadc7571..4c1471c6e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
@@ -43,10 +43,12 @@ class ActorDetailViewModel(
                 _uiState.value = if (user == null) {
                     ActorDetailUIState.Success(actor, isUserSignedIn = false)
                 } else {
-                    val isFavorite = moviesConnector.getIfFavoritedActor.execute(
+                    val favoriteActor = moviesConnector.getIfFavoritedActor.execute(
                         id = user.uid,
                         actorId = UUID.fromString(actorId)
-                    ).data.favoriteActor != null
+                    ).data.favoriteActor
+
+                    val isFavorite = favoriteActor != null
 
                     ActorDetailUIState.Success(
                         actor,

From 287adb178554575cec695a78f164c311783680b6 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 18:50:50 +0100
Subject: [PATCH 49/63] refactor: make error messages nullable and add default
 error message

---
 .../dataconnect/feature/actordetail/ActorDetailUIState.kt | 2 +-
 .../feature/actordetail/ActorDetailViewModel.kt           | 4 ++--
 .../dataconnect/feature/genredetail/GenreDetailUIState.kt | 2 +-
 .../feature/genredetail/GenreDetailViewModel.kt           | 2 +-
 .../dataconnect/feature/moviedetail/MovieDetailUIState.kt | 2 +-
 .../feature/moviedetail/MovieDetailViewModel.kt           | 8 ++++----
 .../example/dataconnect/feature/movies/MoviesUIState.kt   | 2 +-
 .../example/dataconnect/feature/profile/ProfileUIState.kt | 2 +-
 .../dataconnect/feature/profile/ProfileViewModel.kt       | 6 +++---
 .../example/dataconnect/ui/components/ErrorCard.kt        | 6 ++++--
 dataconnect/app/src/main/res/values/strings.xml           | 1 +
 11 files changed, 20 insertions(+), 17 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
index 089ddb1b7..7f8ca2ff5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailUIState.kt
@@ -7,7 +7,7 @@ import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 sealed class ActorDetailUIState {
     data object Loading: ActorDetailUIState()
 
-    data class Error(val errorMessage: String): ActorDetailUIState()
+    data class Error(val errorMessage: String?): ActorDetailUIState()
 
     data class Success(
         // Actor is null if it can't be found on the DB
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
index 4c1471c6e..0ea3b4a2f 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
@@ -57,7 +57,7 @@ class ActorDetailViewModel(
                     )
                 }
             } catch (e: Exception) {
-                _uiState.value = ActorDetailUIState.Error(e.message ?: "")
+                _uiState.value = ActorDetailUIState.Error(e.message)
             }
         }
     }
@@ -76,7 +76,7 @@ class ActorDetailViewModel(
                 // Re-run the query to fetch the actor details
                 fetchActor()
             } catch (e: Exception) {
-                _uiState.value = ActorDetailUIState.Error(e.message ?: "")
+                _uiState.value = ActorDetailUIState.Error(e.message)
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
index fefb51f1d..442776c3c 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailUIState.kt
@@ -6,7 +6,7 @@ sealed class GenreDetailUIState {
 
     data object Loading: GenreDetailUIState()
 
-    data class Error(val errorMessage: String): GenreDetailUIState()
+    data class Error(val errorMessage: String?): GenreDetailUIState()
 
     data class Success(
         val genreName: String,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
index ec507e6c5..2d327d101 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailViewModel.kt
@@ -37,7 +37,7 @@ class GenreDetailViewModel(
                     mostRecent = mostRecent
                 )
             } catch (e: Exception) {
-                _uiState.value = GenreDetailUIState.Error(e.message ?: "")
+                _uiState.value = GenreDetailUIState.Error(e.message)
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
index d1e3bf071..1f6e04028 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailUIState.kt
@@ -6,7 +6,7 @@ import com.google.firebase.dataconnect.movies.GetMovieByIdQuery
 sealed class MovieDetailUIState {
     data object Loading: MovieDetailUIState()
 
-    data class Error(val errorMessage: String): MovieDetailUIState()
+    data class Error(val errorMessage: String?): MovieDetailUIState()
 
     data class Success(
         // Movie is null if it can't be found on the DB
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index 1212c7761..fc19a4c36 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -63,7 +63,7 @@ class MovieDetailViewModel(
                     )
                 }
             } catch (e: Exception) {
-                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+                _uiState.value = MovieDetailUIState.Error(e.message)
             }
         }
     }
@@ -84,7 +84,7 @@ class MovieDetailViewModel(
                 // Re-run the query to fetch movie
                 fetchMovie()
             } catch (e: Exception) {
-                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+                _uiState.value = MovieDetailUIState.Error(e.message)
             }
         }
     }
@@ -105,7 +105,7 @@ class MovieDetailViewModel(
                 // Re-run the query to fetch movie
                 fetchMovie()
             } catch (e: Exception) {
-                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+                _uiState.value = MovieDetailUIState.Error(e.message)
             }
         }
     }
@@ -124,7 +124,7 @@ class MovieDetailViewModel(
                 // Re-run the query to fetch movie
                 fetchMovie()
             } catch (e: Exception) {
-                _uiState.value = MovieDetailUIState.Error(e.message ?: "")
+                _uiState.value = MovieDetailUIState.Error(e.message)
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
index 92b8f70f3..3f428b2e5 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/movies/MoviesUIState.kt
@@ -7,7 +7,7 @@ sealed class MoviesUIState {
 
     data object Loading: MoviesUIState()
 
-    data class Error(val errorMessage: String): MoviesUIState()
+    data class Error(val errorMessage: String?): MoviesUIState()
 
     data class Success(
         val top10movies: List<MoviesTop10Query.Data.MoviesItem>,
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
index 9977d7b77..660c62abe 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileUIState.kt
@@ -5,7 +5,7 @@ import com.google.firebase.dataconnect.movies.GetUserByIdQuery
 sealed class ProfileUIState {
     data object Loading: ProfileUIState()
 
-    data class Error(val errorMessage: String): ProfileUIState()
+    data class Error(val errorMessage: String?): ProfileUIState()
 
     data object AuthState: ProfileUIState()
 
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
index 1620a2959..22a85ea90 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/profile/ProfileViewModel.kt
@@ -54,7 +54,7 @@ class ProfileViewModel(
                 )?.await()
                 moviesConnector.upsertUser.execute(username = displayName)
             } catch (e: Exception) {
-                _uiState.value = ProfileUIState.Error(e.message ?: "")
+                _uiState.value = ProfileUIState.Error(e.message)
                 e.printStackTrace()
             }
         }
@@ -65,7 +65,7 @@ class ProfileViewModel(
             try {
                 auth.signInWithEmailAndPassword(email, password).await()
             } catch (e: Exception) {
-                _uiState.value = ProfileUIState.Error(e.message ?: "")
+                _uiState.value = ProfileUIState.Error(e.message)
             }
         }
     }
@@ -89,7 +89,7 @@ class ProfileViewModel(
                 )
                 Log.d("DisplayUser", "$user")
             } catch (e: Exception) {
-                _uiState.value = ProfileUIState.Error(e.message ?: "")
+                _uiState.value = ProfileUIState.Error(e.message)
             }
         }
     }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
index 08cb86dea..f8c127124 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ErrorCard.kt
@@ -8,12 +8,14 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import com.google.firebase.example.dataconnect.R
 
 @Composable
 fun ErrorCard(
-    errorMessage: String
+    errorMessage: String?
 ) {
     Card(
         colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
@@ -21,7 +23,7 @@ fun ErrorCard(
             .fillMaxWidth()
     ) {
         Text(
-            text = errorMessage,
+            text = errorMessage ?: stringResource(R.string.unknown_error),
             modifier = Modifier.padding(16.dp)
                 .fillMaxWidth()
         )
diff --git a/dataconnect/app/src/main/res/values/strings.xml b/dataconnect/app/src/main/res/values/strings.xml
index d58d70af7..15c39cb40 100644
--- a/dataconnect/app/src/main/res/values/strings.xml
+++ b/dataconnect/app/src/main/res/values/strings.xml
@@ -1,5 +1,6 @@
 <resources>
     <string name="app_name">Firebase Data Connect</string>
+    <string name="unknown_error">An unknown error occurred</string>
 
     <!-- NavBar labels -->
     <string name="label_movies">Movies</string>

From 01e072ab5fee04a5d550f1f532379e8eaa3197e8 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Mon, 30 Sep 2024 19:28:39 +0100
Subject: [PATCH 50/63] refactor: ensure a user is logged in before marking as
 favorite

---
 .../feature/actordetail/ActorDetailViewModel.kt        |  4 +++-
 .../feature/moviedetail/MovieDetailViewModel.kt        | 10 ++++++++--
 .../dataconnect/feature/moviedetail/UserReviews.kt     |  6 ++++--
 3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
index 0ea3b4a2f..99df5ca7e 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailViewModel.kt
@@ -63,13 +63,15 @@ class ActorDetailViewModel(
     }
 
     fun toggleFavorite(newValue: Boolean) {
+        // TODO(thatfiredev): hide the button if there's no user logged in
+        val uid = firebaseAuth.currentUser?.uid ?: return
         viewModelScope.launch {
             try {
                 if (newValue) {
                     moviesConnector.addFavoritedActor.execute(UUID.fromString(actorId))
                 } else {
                     moviesConnector.deleteFavoriteActor.execute(
-                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        userId = uid,
                         actorId = UUID.fromString(actorId)
                     )
                 }
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
index fc19a4c36..90bfd2dfa 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailViewModel.kt
@@ -69,6 +69,8 @@ class MovieDetailViewModel(
     }
 
     fun toggleFavorite(newValue: Boolean) {
+        // TODO(thatfiredev): hide the button if there's no user logged in
+        val uid = firebaseAuth.currentUser?.uid ?: return
         viewModelScope.launch {
             try {
                 if (newValue) {
@@ -77,7 +79,7 @@ class MovieDetailViewModel(
                     // TODO(thatfiredev): investigate whether this is a schema error
                     //    userId probably shouldn't be here.
                     moviesConnector.deleteFavoritedMovie.execute(
-                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        userId = uid,
                         movieId = UUID.fromString(movieId)
                     )
                 }
@@ -90,6 +92,8 @@ class MovieDetailViewModel(
     }
 
     fun toggleWatched(newValue: Boolean) {
+        // TODO(thatfiredev): hide the button if there's no user logged in
+        val uid = firebaseAuth.currentUser?.uid ?: return
         viewModelScope.launch {
             try {
                 if (newValue) {
@@ -98,7 +102,7 @@ class MovieDetailViewModel(
                     // TODO(thatfiredev): investigate whether this is a schema error
                     //    userId probably shouldn't be here.
                     moviesConnector.deleteWatchedMovie.execute(
-                        userId = firebaseAuth.currentUser?.uid ?: "",
+                        userId = uid,
                         movieId = UUID.fromString(movieId)
                     )
                 }
@@ -111,6 +115,8 @@ class MovieDetailViewModel(
     }
 
     fun addRating(rating: Float, text: String) {
+        // TODO(thatfiredev): hide the button if there's no user logged in
+        if (firebaseAuth.currentUser?.uid == null) return
         viewModelScope.launch {
             try {
                 moviesConnector.addReview.execute(
diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt
index 0b677d48d..1ae372360 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/UserReviews.kt
@@ -63,8 +63,10 @@ fun UserReviews(
 
         Button(
             onClick = {
-                onReviewSubmitted(rating, reviewText)
-                reviewText = ""
+                if (!reviewText.isNullOrEmpty()) {
+                    onReviewSubmitted(rating, reviewText)
+                    reviewText = ""
+                }
             }
         ) {
             Text(stringResource(R.string.button_submit_review))

From 607729416fa0a0f17c03d7e41c9fe051221c4637 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Tue, 1 Oct 2024 15:10:18 +0100
Subject: [PATCH 51/63] update GenreDetailScreen

---
 .../dataconnect/feature/genredetail/GenreDetailScreen.kt        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
index c7e9e0e4b..79fddb640 100644
--- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
+++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/genredetail/GenreDetailScreen.kt
@@ -36,7 +36,7 @@ fun GenreDetailScreen(
     uiState: GenreDetailUIState
 ) {
     when (uiState) {
-        GenreDetailUIState.Loading -> LoadingScreen()
+        is GenreDetailUIState.Loading -> LoadingScreen()
 
         is GenreDetailUIState.Error -> ErrorCard(uiState.errorMessage)
 

From 4a4401b4ba75d803de321367ed11aa564227d56a Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:03:36 +0100
Subject: [PATCH 52/63] chore: use v1beta

---
 dataconnect/dataconnect/dataconnect.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dataconnect/dataconnect/dataconnect.yaml b/dataconnect/dataconnect/dataconnect.yaml
index 1eea139ad..8673bd536 100644
--- a/dataconnect/dataconnect/dataconnect.yaml
+++ b/dataconnect/dataconnect/dataconnect.yaml
@@ -1,4 +1,4 @@
-specVersion: "v1alpha"
+specVersion: "v1beta"
 serviceId: "dataconnect"
 location: "us-central1"
 schema:

From 172e457bfb9e959779ac17da6dbc562e25a6d015 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:32:40 +0100
Subject: [PATCH 53/63] ci: remove Data Connect from build

---
 .github/workflows/android.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 55dd574bc..3e97884a2 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -20,6 +20,9 @@ jobs:
       run: python scripts/checksnippets.py
     - name: Copy mock google_services.json
       run: ./copy_mock_google_services_json.sh
+    # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
+    - name: Remove Data Connect from the CI build
+      run: sed -i "" "s/\":dataconnect:app\",//g" settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From 68b25951d4602f5dc60da63163fbe346e181ee4d Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:33:02 +0100
Subject: [PATCH 54/63] chore: upgrade to 16.0.0-beta01

---
 dataconnect/app/build.gradle.kts | 1 +
 gradle/libs.versions.toml        | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts
index 7484eb7c2..3d24730fa 100644
--- a/dataconnect/app/build.gradle.kts
+++ b/dataconnect/app/build.gradle.kts
@@ -73,6 +73,7 @@ dependencies {
     // Firebase dependencies
     implementation(libs.firebase.auth)
     implementation(libs.firebase.dataconnect)
+    implementation(libs.kotlinx.serialization.core)
 
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b4330e046..eedcab13d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,12 +2,13 @@
 agp = "8.6.0"
 coilCompose = "2.6.0"
 firebaseAuth = "23.0.0"
-firebaseDataConnect = "16.0.0-alpha05"
+firebaseDataConnect = "16.0.0-beta01"
 kotlin = "2.0.20"
 coreKtx = "1.13.1"
 junit = "4.13.2"
 junitVersion = "1.1.5"
 espressoCore = "3.5.1"
+kotlinxSerializationCore = "1.6.3"
 lifecycle = "2.8.1"
 activityCompose = "1.9.0"
 composeBom = "2023.08.00"
@@ -35,6 +36,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
 androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
 androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
 compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"}
+kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }

From 8dc1a40856e5cbb6dc8edd9fa112a73dd0ebca37 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:35:03 +0100
Subject: [PATCH 55/63] ci: remove -i parameter

---
 .github/workflows/android.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 3e97884a2..2389fff00 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -22,7 +22,7 @@ jobs:
       run: ./copy_mock_google_services_json.sh
     # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
     - name: Remove Data Connect from the CI build
-      run: sed -i "" "s/\":dataconnect:app\",//g" settings.gradle.kts
+      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From 782010bc7816e18ce3838c892c928008240cf33c Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:37:19 +0100
Subject: [PATCH 56/63] ci: output file

---
 .github/workflows/android.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 2389fff00..37a3a0e07 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -22,7 +22,7 @@ jobs:
       run: ./copy_mock_google_services_json.sh
     # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
     - name: Remove Data Connect from the CI build
-      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts
+      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts >> settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From 851252f6f4565ab43f28466d3b4d6cf66a754262 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:40:52 +0100
Subject: [PATCH 57/63] ci: overwrite instead of append

---
 .github/workflows/android.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 37a3a0e07..525aa157a 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -22,7 +22,7 @@ jobs:
       run: ./copy_mock_google_services_json.sh
     # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
     - name: Remove Data Connect from the CI build
-      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts >> settings.gradle.kts
+      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts > settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From 922c940c6eb774f37fef58e144499d96dcafe8fd Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 19:42:04 +0100
Subject: [PATCH 58/63] chore: agp 8.7.0

---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index eedcab13d..6f0f2f789 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
 [versions]
-agp = "8.6.0"
+agp = "8.7.0"
 coilCompose = "2.6.0"
 firebaseAuth = "23.0.0"
 firebaseDataConnect = "16.0.0-beta01"

From 85f1fb9616d3d4f5b4dd4b45f6804b2d66d40f12 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 20:50:33 +0100
Subject: [PATCH 59/63] testing

---
 .github/workflows/android.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 525aa157a..504c9ad5e 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -23,6 +23,8 @@ jobs:
     # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
     - name: Remove Data Connect from the CI build
       run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts > settings.gradle.kts
+    - name: Testing
+      run: cat settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From a2e39b703e5d85dc5eeae65191155a62fbc60f50 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 20:53:03 +0100
Subject: [PATCH 60/63] revert ci changes

---
 .github/workflows/android.yml | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 504c9ad5e..55dd574bc 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -20,11 +20,6 @@ jobs:
       run: python scripts/checksnippets.py
     - name: Copy mock google_services.json
       run: ./copy_mock_google_services_json.sh
-    # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
-    - name: Remove Data Connect from the CI build
-      run: sed "s/\":dataconnect:app\",//g" settings.gradle.kts > settings.gradle.kts
-    - name: Testing
-      run: cat settings.gradle.kts
     - name: Build with Gradle (Pull Request)
       run: ./build_pull_request.sh
       if: github.event_name == 'pull_request'

From d7fcb9367498f77c476da3f57aae3a8b883b371c Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 21:02:34 +0100
Subject: [PATCH 61/63] ci: add python script to remove fdc

---
 .github/workflows/android.yml | 3 +++
 scripts/ci_remove_fdc.py      | 8 ++++++++
 2 files changed, 11 insertions(+)
 create mode 100644 scripts/ci_remove_fdc.py

diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 55dd574bc..8a635beff 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -18,6 +18,9 @@ jobs:
       uses: gradle/gradle-build-action@v2
     - name: Check Snippets
       run: python scripts/checksnippets.py
+    # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
+    - name: Remove Firebase Data Connect from CI
+      run: python scripts/ci_remove_fdc.py
     - name: Copy mock google_services.json
       run: ./copy_mock_google_services_json.sh
     - name: Build with Gradle (Pull Request)
diff --git a/scripts/ci_remove_fdc.py b/scripts/ci_remove_fdc.py
new file mode 100644
index 000000000..c425af615
--- /dev/null
+++ b/scripts/ci_remove_fdc.py
@@ -0,0 +1,8 @@
+# TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
+with open('settings.gradle.kts', 'r') as file:
+  filedata = file.read()
+
+filedata = filedata.replace('":dataconnect:app",', '')
+
+with open('settings.gradle.kts', 'w') as file:
+  file.write(filedata)

From d4249df07fc818a6f8d62003d5962aba52251f62 Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 21:22:13 +0100
Subject: [PATCH 62/63] docs: update README.md

---
 dataconnect/README.md                         | 56 ++++++++-----------
 .../gradle/wrapper/gradle-wrapper.properties  |  2 +-
 2 files changed, 25 insertions(+), 33 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 761f54539..3d5b03513 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -11,6 +11,11 @@ For more information about Firebase Data Connect visit [the docs](https://fireba
 Follow these steps to get up and running with Firebase Data Connect. For more detailed instructions,
 check out the [official documentation](https://firebase.google.com/docs/data-connect/quickstart).
 
+### 0. Prerequisites
+- Latest version of [Android Studio](https://developer.android.com/studio)
+- Latest version of [Visual Studio Code](https://code.visualstudio.com/)
+- The [Firebase Data Connect VS Code Extension](https://marketplace.visualstudio.com/items?itemName=GoogleCloudTools.firebase-dataconnect-vscode)
+
 ### 1. Connect to your Firebase project
 
 1. If you haven't already, create a Firebase project.
@@ -35,28 +40,26 @@ check out the [official documentation](https://firebase.google.com/docs/data-con
 4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance
    can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
 
-5. If you haven’t already, add an Android app to your Firebase project, with the android package name `com.google.firebase.example.dataconnect`. Download and then add the Firebase Android configuration file (`google-services.json`) to your app:
-    1. Click **Download google-services.json** to obtain your Firebase Android config file.
-    2. Move your config file into the `quickstart-android/dataconnect/app` directory.
-
-### 2. Set Up Firebase CLI
-
-Ensure the Firebase CLI is installed and up to date:
+5. If you haven’t already, add an Android app to your Firebase project, with the android package name `com.google.firebase.example.dataconnect`.
+ Click **Download google-services.json** to obtain your Firebase Android config file.
 
-```bash
-npm install -g firebase-tools
-```
-
-### 3. Cloning the repository
-This repository contains the quickstart to get started with the functionalities of Data Connect.
+### 2. Cloning the repository
 
 1. Clone this repository to your local machine:
    ```sh
    git clone https://github.com/firebase/quickstart-android.git
    ```
 
-2. (Private Preview only) Checkout the `fdc-quickstart` branch (`git checkout fdc-quickstart`)
-   and open the project in Android Studio.
+2. Move the `google-services.json` config file (downloaded in the previous step) into the
+  `quickstart-android/dataconnect/app/` directory.
+
+### 3. Open in Visual Studio Code (VS Code)
+
+1. Open the `quickstart-android/dataconnect` directory in VS Code.
+2. Click on the Firebase Data Connect icon on the VS Code sidebar to load the Extension.
+   a. Sign in with your Google Account if you haven't already.
+3. Click on "Connect a Firebase project" and choose the project where you have set up Data Connect.
+4. Click on "Start Emulators" - this should generate the Kotlin SDK for you and start the emulators.
 
 ### 4. Deploy the service to Firebase and generate SDKs
 
@@ -74,24 +77,13 @@ This repository contains the quickstart to get started with the functionalities
    firebase dataconnect:sdk:generate
    ```
 
-### 5. Populating the database
-1. Run `1_movie_insert.gql`, `2_actor_insert.gql`, `3_movie_actor_insert.gql`, and `4_user_favorites_review_insert.gql` files in the `./dataconnect` directory in order using the VS code extension,
+### 5. Populate the database
+In VS Code, open the `quickstart-android/dataconnect/dataconnect/data_seed.gql` file and click the
+ `Run (local)` button at the top of the file.
+
+If you’d like to confirm that the data was correctly inserted,
+open `quickstart-android/dataconnect/connectors/queries.gql` and run the `ListMovies` query.
 
 ### 6. Running the app
 
 Press the Run button in Android Studio to run the sample app on your device.
-
-## 🚧 Work in Progress
-
-This app is still missing some features which will be added before Public Preview:
-
-- [ ] Search
-- [ ] Movie review
-  - [x] Add a new review 
-  - [ ] Update a review
-  - [ ] Delete a review
-- [x] Actors
-  - [x] Show actor profile
-  - [x] Mark actor as favorite
-- [ ] Error handling
-  Some errors may cause the app to crash, especially if there's no user logged in. 
diff --git a/dataconnect/gradle/wrapper/gradle-wrapper.properties b/dataconnect/gradle/wrapper/gradle-wrapper.properties
index 367d4dffd..ddf39bde2 100644
--- a/dataconnect/gradle/wrapper/gradle-wrapper.properties
+++ b/dataconnect/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Wed May 08 19:29:05 BST 2024
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

From 83c7a3d68a865dc063fbc2369ac414e5444aa23d Mon Sep 17 00:00:00 2001
From: rosariopf <rosariopf@google.com>
Date: Wed, 2 Oct 2024 21:31:23 +0100
Subject: [PATCH 63/63] docs: remove deploy step

---
 dataconnect/README.md | 20 ++------------------
 1 file changed, 2 insertions(+), 18 deletions(-)

diff --git a/dataconnect/README.md b/dataconnect/README.md
index 3d5b03513..73160a9df 100644
--- a/dataconnect/README.md
+++ b/dataconnect/README.md
@@ -61,29 +61,13 @@ check out the [official documentation](https://firebase.google.com/docs/data-con
 3. Click on "Connect a Firebase project" and choose the project where you have set up Data Connect.
 4. Click on "Start Emulators" - this should generate the Kotlin SDK for you and start the emulators.
 
-### 4. Deploy the service to Firebase and generate SDKs
-
-1. Open the `quickstart-android/dataconnect/dataconnect` directory and deploy the schema with
-    the following command:
-    ```bash
-    firebase deploy
-    ```
-2. Once the deploy is complete, you should be able to see the movie schema in the
- [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
- of the Firebase console.
-
-3. Generate the Kotlin SDK by running:
-   ```bash
-   firebase dataconnect:sdk:generate
-   ```
-
-### 5. Populate the database
+### 4. Populate the database
 In VS Code, open the `quickstart-android/dataconnect/dataconnect/data_seed.gql` file and click the
  `Run (local)` button at the top of the file.
 
 If you’d like to confirm that the data was correctly inserted,
 open `quickstart-android/dataconnect/connectors/queries.gql` and run the `ListMovies` query.
 
-### 6. Running the app
+### 5. Running the app
 
 Press the Run button in Android Studio to run the sample app on your device.