diff --git a/admob/app/build.gradle b/admob/app/build.gradle index 1a388abe50..41007b0a05 100644 --- a/admob/app/build.gradle +++ b/admob/app/build.gradle @@ -8,17 +8,20 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.samples.quickstart.admobexample' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.samples.quickstart.admobexample" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } } buildTypes { @@ -36,6 +39,17 @@ android { buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' } } @@ -47,19 +61,30 @@ dependencies { implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'com.google.android.gms:play-services-ads:21.3.0' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. implementation 'com.google.firebase:firebase-analytics' debugImplementation "androidx.fragment:fragment-testing:1.5.4" - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + + debugImplementation "androidx.fragment:fragment-testing:1.5.2" + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/admob/app/src/main/AndroidManifest.xml b/admob/app/src/main/AndroidManifest.xml index aeddd6ffc3..fdd24ea521 100644 --- a/admob/app/src/main/AndroidManifest.xml +++ b/admob/app/src/main/AndroidManifest.xml @@ -36,6 +36,7 @@ + diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt index adc6fee47c..cc69a5f048 100644 --- a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt @@ -12,11 +12,15 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { Choice( "Java", "Run the Firebase Admob quickstart written in Java.", - Intent(this, MainActivity::class.java)), + Intent(this, com.google.samples.quickstart.admobexample.java.MainActivity::class.java)), Choice( "Kotlin", "Run the Firebase Admob quickstart written in Kotlin.", - Intent(this, com.google.samples.quickstart.admobexample.kotlin.MainActivity::class.java)) + Intent(this, com.google.samples.quickstart.admobexample.kotlin.MainActivity::class.java)), + Choice( + "Compose", + "Run the Firebase Admob quickstart written in Compose.", + Intent(this, com.google.samples.quickstart.admobexample.kotlin.MainComposeActivity::class.java)) ) } } diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainComposeActivity.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainComposeActivity.kt new file mode 100644 index 0000000000..04511c0333 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainComposeActivity.kt @@ -0,0 +1,207 @@ +package com.google.samples.quickstart.admobexample.kotlin + +import android.content.ContentValues.TAG +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.material.Surface +import androidx.compose.material.TopAppBar +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.google.android.gms.ads.AdError +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdSize +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.FullScreenContentCallback +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.MobileAds +import com.google.samples.quickstart.admobexample.kotlin.ui.theme.AdmobTheme +import com.google.samples.quickstart.admobexample.R +import com.google.android.gms.ads.interstitial.InterstitialAd +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback + +class MainComposeActivity : ComponentActivity() { + private var mInterstitialAd: InterstitialAd? = null + private lateinit var adUnitId: String //="ca-app-pub-3940256099942544/1033173712" //could be set to another id + private val buttonClickLambda = { displayNewInterstitial() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + adUnitId = getString(R.string.interstitial_ad_unit_id) + + setContent { + AdmobTheme { + //A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + MainAppView( buttonClickEventAdLoader = buttonClickLambda ) // call Composable UI + } + } + + initializeInterstitial() + } + } + + private fun setInterstitialCallback() { + + + mInterstitialAd?.fullScreenContentCallback = object: FullScreenContentCallback() { + override fun onAdClicked() { + // Called when a click is recorded for an ad. + Log.d(TAG, "Ad was clicked.") + } + + override fun onAdDismissedFullScreenContent() { + // Called when ad is dismissed. + Log.d(TAG, "Ad dismissed fullscreen content.") + mInterstitialAd = null + initializeInterstitial() // get a new ad + } + + override fun onAdFailedToShowFullScreenContent(p0: AdError) { + // Called when ad fails to show. + Log.e(TAG, "Ad failed to show fullscreen content.") + mInterstitialAd = null + initializeInterstitial() // get a new ad + } + + override fun onAdImpression() { + // Called when an impression is recorded for an ad. + Log.d(TAG, "Ad recorded an impression.") + } + + override fun onAdShowedFullScreenContent() { + // Called when ad is shown. + Log.d(TAG, "Ad showed fullscreen content.") + } + } + } + + private fun initializeInterstitial(){ + MobileAds.initialize(this) + val adRequest = AdRequest.Builder().build() + + InterstitialAd.load(this, adUnitId, adRequest, object : InterstitialAdLoadCallback() { + override fun onAdFailedToLoad(adError: LoadAdError) { + Log.d(TAG, adError.toString()) + mInterstitialAd = null + } + + override fun onAdLoaded(interstitialAd: InterstitialAd) { + Log.d(TAG, "Ad was loaded.") + mInterstitialAd = interstitialAd + } + }) + } + + fun displayNewInterstitial(){ + if (mInterstitialAd != null) { // ad is available + setInterstitialCallback() // set the callback methods + mInterstitialAd?.show(this) + } else { // ad is not available + Log.d("TAG", "The interstitial ad wasn't ready yet.") + initializeInterstitial() + } + } + + +} + + +@Composable +fun MainAppView(modifier: Modifier = Modifier, buttonClickEventAdLoader : () -> Unit = {}){ + Scaffold( + topBar = { // top bar with app name + TopAppBar( + backgroundColor = colorResource(R.color.colorPrimary) + ) { + androidx.compose.material.Text( + text = stringResource(R.string.app_name), + style = androidx.compose.material.MaterialTheme.typography.h6, + textAlign = TextAlign.Center, + modifier = Modifier.padding(8.dp), + color = Color.White + ) + } + }, + content = { + + Column( + modifier = Modifier + .padding(it) + .fillMaxWidth() + .fillMaxHeight(), + + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(24.dp)) + + Image(painter = painterResource(R.drawable.firebase_lockup_400), contentDescription = "") + Spacer(modifier = Modifier.height(160.dp)) + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorAccent)), + onClick = { buttonClickEventAdLoader() } //lambda for onClick action + ) { + Text( + text = stringResource(R.string.interstitial_button_text), + fontSize = 24.sp, + color = Color.White + ) + } + + } + }, + bottomBar = { // keeps the banner ad at the bottom! + AdvertBanner() + } + ) + +} + + +@Composable +fun AdvertBanner(modifier: Modifier = Modifier) { // banner advert + + Row( + modifier = Modifier + .fillMaxWidth() + ) { + AndroidView( + modifier = modifier.fillMaxWidth(), + factory = { context -> + AdView(context).apply { + setAdSize(AdSize.BANNER) + adUnitId = context.getString(R.string.banner_ad_unit_id) + loadAd(AdRequest.Builder().build()) + } + } + ) + } + +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Color.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Color.kt new file mode 100644 index 0000000000..ca187c6eeb --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Color.kt @@ -0,0 +1,13 @@ +package com.google.samples.quickstart.admobexample.kotlin.ui.theme + +import androidx.compose.ui.graphics.Color +import com.google.samples.quickstart.admobexample.R + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +// self-defined light-mode colour scheme +val FirebaseBlue = Color(0xFF0288D1) // copied from colors.xml +val FirebaseBannerBlue = Color(0xFF039BE5) // copied from colors.xml +val FirebaseOrange = Color(0xFFFFA000) // copied from colors.xml \ No newline at end of file diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Shape.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Shape.kt new file mode 100644 index 0000000000..cb1986dc6e --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.google.samples.quickstart.admobexample.kotlin.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(16.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Theme.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Theme.kt new file mode 100644 index 0000000000..2ae9d75d42 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Theme.kt @@ -0,0 +1,49 @@ +package com.google.samples.quickstart.admobexample.kotlin.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple80, + primaryVariant = PurpleGrey80, + secondary = Pink80 +) + +private val LightColorPalette = lightColors( + primary = FirebaseBlue, + primaryVariant = FirebaseBannerBlue, + secondary = FirebaseOrange + + /* 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 AdmobTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} + diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Type.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Type.kt new file mode 100644 index 0000000000..d4b748c801 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.google.samples.quickstart.admobexample.kotlin.ui.theme + +import androidx.compose.material.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( + body1 = 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/admob/build.gradle b/admob/build.gradle index 264bea7c6f..d577ca24d5 100644 --- a/admob/build.gradle +++ b/admob/build.gradle @@ -1,6 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + compose_version = '1.3.0' + } repositories { mavenLocal() google() @@ -9,7 +12,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/analytics/app/build.gradle b/analytics/app/build.gradle index f4c2adbf3c..53a4704f25 100644 --- a/analytics/app/build.gradle +++ b/analytics/app/build.gradle @@ -8,16 +8,19 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.analytics' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.quickstart.analytics" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } } buildTypes { @@ -29,6 +32,22 @@ android { buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -41,9 +60,10 @@ dependencies { implementation "androidx.preference:preference-ktx:1.2.0" // Needed to override the version used by preference-ktx implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Analytics (Java) implementation 'com.google.firebase:firebase-analytics' @@ -51,8 +71,17 @@ dependencies { // Firebase Analytics (Kotlin) implementation 'com.google.firebase:firebase-analytics-ktx' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/analytics/build.gradle b/analytics/build.gradle index 904edcd299..219cc10cf3 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/appdistribution/app/build.gradle b/appdistribution/app/build.gradle index d0f7200f88..cb4c85c269 100644 --- a/appdistribution/app/build.gradle +++ b/appdistribution/app/build.gradle @@ -6,15 +6,18 @@ plugins { android { namespace 'com.google.firebase.appdistributionquickstart' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.appdistributionquickstart" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -28,6 +31,22 @@ android { buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } lint { warning 'InvalidPackage' @@ -41,9 +60,10 @@ dependencies { implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // ADD the SDK to the "prerelease" variant only (example) implementation 'com.google.firebase:firebase-appdistribution-ktx:16.0.0-beta01' @@ -52,8 +72,17 @@ dependencies { // for Google Analytics. This is recommended, but not required. implementation 'com.google.firebase:firebase-analytics' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/appdistribution/build.gradle b/appdistribution/build.gradle index 9332ee6330..64c31555c4 100644 --- a/appdistribution/build.gradle +++ b/appdistribution/build.gradle @@ -10,7 +10,7 @@ buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.14' classpath 'com.android.tools.build:gradle:7.3.1' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/auth/app/build.gradle b/auth/app/build.gradle index c90259eac5..497f109737 100644 --- a/auth/app/build.gradle +++ b/auth/app/build.gradle @@ -13,11 +13,14 @@ android { defaultConfig { applicationId "com.google.firebase.quickstart.auth" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -28,13 +31,24 @@ android { } } - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } - buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -49,9 +63,17 @@ dependencies { implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Authentication (Java) implementation 'com.google.firebase:firebase-auth' @@ -60,7 +82,7 @@ dependencies { implementation 'com.google.firebase:firebase-auth-ktx' // Google Identity Services SDK (only required for Auth with Google) - implementation 'com.google.android.gms:play-services-auth:20.4.0' + implementation 'com.google.android.gms:play-services-auth:20.3.0' // Firebase UI // Used in FirebaseUIActivity. @@ -71,7 +93,10 @@ dependencies { implementation 'com.facebook.android:facebook-login:13.2.0' implementation 'androidx.browser:browser:1.0.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/auth/build.gradle b/auth/build.gradle index 8d0a48998d..480ec3bab7 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/build.gradle b/build.gradle index ee2372acf8..86337508f2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,7 @@ buildscript { + ext { + compose_version = '1.2.1' + } repositories { google() mavenCentral() @@ -6,7 +9,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' classpath 'com.google.gms:google-services:4.3.14' classpath 'com.google.firebase:perf-plugin:1.4.2' @@ -57,7 +60,11 @@ configurations { } dependencies { - ktlint "com.github.shyiko:ktlint:0.31.0" + ktlint ("com.pinterest:ktlint:0.48.2") { + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL)) + } + } } task("ktlint", type: JavaExec, group: "verification") { @@ -72,7 +79,7 @@ task("ktlint", type: JavaExec, group: "verification") { description = "Check Kotlin code style." classpath = configurations.ktlint - main = "com.github.shyiko.ktlint.Main" + mainClass.set("com.pinterest.ktlint.Main") args = [ "--format", "--android", diff --git a/config/app/build.gradle b/config/app/build.gradle index 17f01745ba..b53a787ced 100644 --- a/config/app/build.gradle +++ b/config/app/build.gradle @@ -12,23 +12,42 @@ android { defaultConfig { applicationId "com.google.samples.quickstart.config" - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } } buildTypes { release { - minifyEnabled true + minifyEnabled = true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -37,6 +56,7 @@ dependencies { implementation project(":internal:chooserx") implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation platform('com.google.firebase:firebase-bom:31.1.0') @@ -50,9 +70,21 @@ dependencies { // For an optimal experience using Remote Config, add the Firebase SDK // for Google Analytics. This is recommended, but not required. implementation 'com.google.firebase:firebase-analytics' + implementation 'androidx.compose.material:material:1.3.1' + + debugImplementation "androidx.fragment:fragment-testing:1.5.4" + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/config/app/src/main/AndroidManifest.xml b/config/app/src/main/AndroidManifest.xml index 6065a87b1b..ed2b8ff18a 100644 --- a/config/app/src/main/AndroidManifest.xml +++ b/config/app/src/main/AndroidManifest.xml @@ -5,20 +5,23 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/AppTheme" > - + android:theme="@style/AppTheme"> + + - - + android:exported="true" + android:label="@string/app_name"> @@ -27,4 +30,4 @@ - + \ No newline at end of file diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/EntryChoiceActivity.kt b/config/app/src/main/java/com/google/samples/quickstart/config/EntryChoiceActivity.kt index e6ffe34e8d..47c0d5c45a 100644 --- a/config/app/src/main/java/com/google/samples/quickstart/config/EntryChoiceActivity.kt +++ b/config/app/src/main/java/com/google/samples/quickstart/config/EntryChoiceActivity.kt @@ -8,18 +8,30 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { override fun getChoices(): List { return listOf( - Choice( - "Java", - "Run the Firebase Remote Config quickstart written in Java.", - Intent( - this, - com.google.samples.quickstart.config.java.MainActivity::class.java)), - Choice( - "Kotlin", - "Run the Firebase Remote Config quickstart written in Kotlin.", - Intent( - this, - com.google.samples.quickstart.config.kotlin.MainActivity::class.java)) + Choice( + "Java", + "Run the Firebase Remote Config quickstart written in Java.", + Intent( + this, + com.google.samples.quickstart.config.java.MainActivity::class.java + ) + ), + Choice( + "Kotlin", + "Run the Firebase Remote Config quickstart written in Kotlin.", + Intent( + this, + com.google.samples.quickstart.config.kotlin.MainActivity::class.java + ) + ), + Choice( + "Compose", + "Run the Firebase Remote Config quickstart written in Compose.", + Intent( + this, + com.google.samples.quickstart.config.kotlin.MainComposeActivity::class.java + ) + ) ) } } diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/java/MainActivity.java b/config/app/src/main/java/com/google/samples/quickstart/config/java/MainActivity.java index e4c537568e..9143ad64e7 100644 --- a/config/app/src/main/java/com/google/samples/quickstart/config/java/MainActivity.java +++ b/config/app/src/main/java/com/google/samples/quickstart/config/java/MainActivity.java @@ -65,27 +65,21 @@ public void onClick(View v) { }); // Get Remote Config instance. - // [START get_remote_config_instance] mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance(); - // [END get_remote_config_instance] // Create a Remote Config Setting to enable developer mode, which you can use to increase // the number of fetches available per hour during development. Also use Remote Config // Setting to set the minimum fetch interval. - // [START enable_dev_mode] FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder() .setMinimumFetchIntervalInSeconds(3600) .build(); mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings); - // [END enable_dev_mode] // Set default Remote Config parameter values. An app uses the in-app default values, and // when you need to adjust those defaults, you set an updated value for only the values you // want to change in the Firebase console. See Best Practices in the README for more // information. - // [START set_default_values] mFirebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_defaults); - // [END set_default_values] fetchWelcome(); } @@ -96,7 +90,6 @@ public void onClick(View v) { private void fetchWelcome() { mWelcomeTextView.setText(mFirebaseRemoteConfig.getString(LOADING_PHRASE_CONFIG_KEY)); - // [START fetch_config_with_callback] mFirebaseRemoteConfig.fetchAndActivate() .addOnCompleteListener(this, new OnCompleteListener() { @Override @@ -114,18 +107,14 @@ public void onComplete(@NonNull Task task) { displayWelcomeMessage(); } }); - // [END fetch_config_with_callback] } /** * Display a welcome message in all caps if welcome_message_caps is set to true. Otherwise, * display a welcome message as fetched from welcome_message. */ - // [START display_welcome_message] private void displayWelcomeMessage() { - // [START get_config_values] String welcomeMessage = mFirebaseRemoteConfig.getString(WELCOME_MESSAGE_KEY); - // [END get_config_values] if (mFirebaseRemoteConfig.getBoolean(WELCOME_MESSAGE_CAPS_KEY)) { mWelcomeTextView.setAllCaps(true); } else { @@ -133,5 +122,4 @@ private void displayWelcomeMessage() { } mWelcomeTextView.setText(welcomeMessage); } - // [END display_welcome_message] } diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainActivity.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainActivity.kt index 90a38e3941..9b191c78ea 100644 --- a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainActivity.kt +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainActivity.kt @@ -1,99 +1,38 @@ package com.google.samples.quickstart.config.kotlin import android.os.Bundle -import android.util.Log -import android.widget.Toast +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import com.google.firebase.ktx.Firebase -import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.google.firebase.remoteconfig.ktx.get -import com.google.firebase.remoteconfig.ktx.remoteConfig -import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import androidx.lifecycle.lifecycleScope import com.google.samples.quickstart.config.R import com.google.samples.quickstart.config.databinding.ActivityMainBinding +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { - - private lateinit var remoteConfig: FirebaseRemoteConfig private lateinit var binding: ActivityMainBinding + private val viewModel: RemoteConfigViewModel by viewModels { RemoteConfigViewModel.Factory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - binding.fetchButton.setOnClickListener { fetchWelcome() } - - // Get Remote Config instance. - // [START get_remote_config_instance] - remoteConfig = Firebase.remoteConfig - // [END get_remote_config_instance] + binding.fetchButton.setOnClickListener { viewModel.fetchRemoteConfig() } - // Create a Remote Config Setting to enable developer mode, which you can use to increase - // the number of fetches available per hour during development. Also use Remote Config - // Setting to set the minimum fetch interval. - // [START enable_dev_mode] - val configSettings = remoteConfigSettings { - minimumFetchIntervalInSeconds = 3600 - } - remoteConfig.setConfigSettingsAsync(configSettings) - // [END enable_dev_mode] + viewModel.enableDeveloperMode() - // Set default Remote Config parameter values. An app uses the in-app default values, and - // when you need to adjust those defaults, you set an updated value for only the values you - // want to change in the Firebase console. See Best Practices in the README for more - // information. - // [START set_default_values] - remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults) - // [END set_default_values] + viewModel.setDefaultValues(R.xml.remote_config_defaults) - fetchWelcome() - } - - /** - * Fetch a welcome message from the Remote Config service, and then activate it. - */ - private fun fetchWelcome() { - binding.welcomeTextView.text = remoteConfig[LOADING_PHRASE_CONFIG_KEY].asString() - - // [START fetch_config_with_callback] - remoteConfig.fetchAndActivate() - .addOnCompleteListener(this) { task -> - if (task.isSuccessful) { - val updated = task.result - Log.d(TAG, "Config params updated: $updated") - Toast.makeText(this, "Fetch and activate succeeded", - Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(this, "Fetch failed", - Toast.LENGTH_SHORT).show() - } - displayWelcomeMessage() - } - // [END fetch_config_with_callback] - } - - /** - * Display a welcome message in all caps if welcome_message_caps is set to true. Otherwise, - * display a welcome message as fetched from welcome_message. - */ - // [START display_welcome_message] - private fun displayWelcomeMessage() { - // [START get_config_values] - val welcomeMessage = remoteConfig[WELCOME_MESSAGE_KEY].asString() - // [END get_config_values] - binding.welcomeTextView.isAllCaps = remoteConfig[WELCOME_MESSAGE_CAPS_KEY].asBoolean() - binding.welcomeTextView.text = welcomeMessage - } - - companion object { - - private const val TAG = "MainActivity" + lifecycleScope.launch { + viewModel.welcomeMessage.collect { welcomeMessage -> + binding.welcomeTextView.text = welcomeMessage + } + } - // Remote Config keys - private const val LOADING_PHRASE_CONFIG_KEY = "loading_phrase" - private const val WELCOME_MESSAGE_KEY = "welcome_message" - private const val WELCOME_MESSAGE_CAPS_KEY = "welcome_message_caps" + lifecycleScope.launch { + viewModel.allCaps.collect { isWelcomeAllCaps -> + binding.welcomeTextView.isAllCaps = isWelcomeAllCaps + } + } } - // [END display_welcome_message] } diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainComposeActivity.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainComposeActivity.kt new file mode 100644 index 0000000000..30d9764d57 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/MainComposeActivity.kt @@ -0,0 +1,151 @@ +package com.google.samples.quickstart.config.kotlin + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Spacer + +import androidx.compose.material.Scaffold +import androidx.compose.material.TopAppBar +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.rememberScaffoldState + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.samples.quickstart.config.R +import com.google.samples.quickstart.config.kotlin.ui.theme.ConfigTheme +import kotlinx.coroutines.launch + +class MainComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ConfigTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + MainAppView() + } + } + } + } +} + +@Composable +fun MainAppView( + modifier: Modifier = Modifier, + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + remoteConfigViewModel: RemoteConfigViewModel = viewModel(factory = RemoteConfigViewModel.Factory) +) { + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + // Configure Firebase Remote Config when the screen is created + if (event == Lifecycle.Event.ON_CREATE) { + remoteConfigViewModel.enableDeveloperMode() + remoteConfigViewModel.setDefaultValues(R.xml.remote_config_defaults) + } + } + + lifecycleOwner.lifecycle.addObserver(observer) + + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + + val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState` + val coroutineScope = rememberCoroutineScope() + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + TopAppBar( + backgroundColor = colorResource(R.color.colorPrimary) + ) { + Text( + text = stringResource(R.string.app_name), + style = androidx.compose.material.MaterialTheme.typography.h6, + textAlign = TextAlign.Center, + modifier = Modifier.padding(8.dp), + color = Color.White + ) + } + }, content = { + Column(modifier = Modifier.padding(it).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.height(24.dp)) + + Image(painter = painterResource(R.drawable.firebase_lockup_400), contentDescription = "") + + // Text displayed + val remoteConfigDisplayText by remoteConfigViewModel.welcomeMessage.collectAsState() + + val allCaps by remoteConfigViewModel.allCaps.collectAsState() + + Text( + text = if (allCaps) { + remoteConfigDisplayText.uppercase() + } else { + remoteConfigDisplayText + }, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(160.dp)) + + // Button to fetch remote welcome + Button( + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorAccent)), + onClick = { + // Fetch config and update the display text + remoteConfigViewModel.fetchRemoteConfig() + // Display Scaffold + coroutineScope.launch { // using the `coroutineScope` to `launch` showing the snackbar + // taking the `snackbarHostState` from the attached `scaffoldState` + val snackbarResult = scaffoldState.snackbarHostState.showSnackbar( + message = "Fetching remote message..." + ) + } + } + ) { + Text( + text = stringResource(R.string.fetch_remote_welcome_message), + fontSize = 20.sp, + color = Color.White + ) + } + } + }) + + + +} + diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/RemoteConfigViewModel.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/RemoteConfigViewModel.kt new file mode 100644 index 0000000000..9f54bc7910 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/RemoteConfigViewModel.kt @@ -0,0 +1,96 @@ +package com.google.samples.quickstart.config.kotlin + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.get +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +class RemoteConfigViewModel( + private val remoteConfig: FirebaseRemoteConfig +) : ViewModel() { + private val _welcomeMessage = MutableStateFlow("Welcome...") + val welcomeMessage: StateFlow = _welcomeMessage + + private val _allCaps = MutableStateFlow(false) + val allCaps: StateFlow = _allCaps + + fun enableDeveloperMode() { + viewModelScope.launch { + // Create a Remote Config Setting to enable developer mode, which you can use to increase + // the number of fetches available per hour during development. Also use Remote Config + // Setting to set the minimum fetch interval. + val configSettings = remoteConfigSettings { + minimumFetchIntervalInSeconds = 3600 + } + remoteConfig.setConfigSettingsAsync(configSettings).await() + } + } + + fun setDefaultValues(defaultValuesXml: Int) { + viewModelScope.launch { + // Set default Remote Config parameter values. An app uses the in-app default values, and + // when you need to adjust those defaults, you set an updated value for only the values you + // want to change in the Firebase console. See Best Practices in the README for more + // information. + remoteConfig.setDefaultsAsync(defaultValuesXml).await() + + // Update the UI with the default parameter values + updateUI() + } + } + + fun fetchRemoteConfig() { + _welcomeMessage.value = remoteConfig[LOADING_PHRASE_CONFIG_KEY].asString() + + viewModelScope.launch { + try { + val updated = remoteConfig.fetchAndActivate().await() + Log.d(TAG, "Config params updated: $updated") + + // Update the UI with the fetched parameter values + updateUI() + } catch (e: Exception) { + Log.e(TAG, "There was an error fetching and activating your config") + _welcomeMessage.value = e.message ?: "Unknown Error" + } + } + } + + private fun updateUI() { + _welcomeMessage.value = remoteConfig[WELCOME_MESSAGE_KEY].asString() + _allCaps.value = remoteConfig[WELCOME_MESSAGE_CAPS_KEY].asBoolean() + } + + companion object { + const val TAG = "RemoteConfigViewModel" + + // Remote Config keys + private const val LOADING_PHRASE_CONFIG_KEY = "loading_phrase" + private const val WELCOME_MESSAGE_KEY = "welcome_message" + private const val WELCOME_MESSAGE_CAPS_KEY = "welcome_message_caps" + + // Used to inject this ViewModel's dependencies + // See also: https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras + ): T { + // Get Remote Config instance. + val remoteConfig = Firebase.remoteConfig + return RemoteConfigViewModel(remoteConfig) as T + } + } + } +} diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Color.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Color.kt new file mode 100644 index 0000000000..ae14d722f3 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.google.samples.quickstart.config.kotlin.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val FirebaseBlue = Color(0xFF0288D1) // copied from colors.xml +val FirebaseBannerBlue = Color(0xFF039BE5) // copied from colors.xml +val FirebaseOrange = Color(0xFFFFA000) // copied from colors.xml diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Shape.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Shape.kt new file mode 100644 index 0000000000..1c1509ac47 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.google.samples.quickstart.config.kotlin.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(16.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Theme.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Theme.kt new file mode 100644 index 0000000000..c3157d1e61 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Theme.kt @@ -0,0 +1,47 @@ +package com.google.samples.quickstart.config.kotlin.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple80, + primaryVariant = PurpleGrey80, + secondary = Pink80 +) + +private val LightColorPalette = lightColors( + primary = Purple80, + primaryVariant = PurpleGrey80, + secondary = Pink80 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun ConfigTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Type.kt b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Type.kt new file mode 100644 index 0000000000..7d8bc30dd7 --- /dev/null +++ b/config/app/src/main/java/com/google/samples/quickstart/config/kotlin/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.google.samples.quickstart.config.kotlin.ui.theme + +import androidx.compose.material.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( + body1 = 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 + ) + */ +) diff --git a/config/app/src/main/res/values/strings.xml b/config/app/src/main/res/values/strings.xml index f03807996a..3f18f23cec 100644 --- a/config/app/src/main/res/values/strings.xml +++ b/config/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ Firebase Remote Config - fetch remote welcome + Fetch remote welcome diff --git a/config/build.gradle b/config/build.gradle index 98de3b7f07..4f8407c741 100644 --- a/config/build.gradle +++ b/config/build.gradle @@ -1,6 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + compose_version = '1.3.0' + } repositories { mavenLocal() google() @@ -9,7 +12,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/config/gradle.properties b/config/gradle.properties index aac7c9b461..29b531a1d1 100644 --- a/config/gradle.properties +++ b/config/gradle.properties @@ -10,7 +10,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m - +android.useAndroidX=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/crash/app/build.gradle b/crash/app/build.gradle index 951e3acca1..daf6e7d972 100644 --- a/crash/app/build.gradle +++ b/crash/app/build.gradle @@ -9,14 +9,17 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.samples.quickstart.crash' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.samples.quickstart.crash" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -34,6 +37,22 @@ android { buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -42,9 +61,11 @@ dependencies { implementation project(":internal:chooserx") implementation 'com.google.android.material:material:1.7.0' implementation "androidx.activity:activity-ktx:1.6.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Crashlytics (Kotlin) implementation 'com.google.firebase:firebase-crashlytics-ktx' @@ -56,9 +77,18 @@ dependencies { // For use in the CustomKeySamples -- for testing Google Api Availability. implementation 'com.google.android.gms:play-services-base:18.1.0' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/crash/build.gradle b/crash/build.gradle index b482c29d2e..253a0e83f0 100644 --- a/crash/build.gradle +++ b/crash/build.gradle @@ -10,7 +10,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/database/app/build.gradle b/database/app/build.gradle index 358bc33528..57305efb97 100644 --- a/database/app/build.gradle +++ b/database/app/build.gradle @@ -8,15 +8,18 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.database' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.quickstart.database" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -30,6 +33,22 @@ android { buildFeatures { viewBinding = true + compose true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -42,9 +61,11 @@ dependencies { implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Realtime Database (Java) implementation 'com.google.firebase:firebase-database' @@ -60,11 +81,20 @@ dependencies { implementation 'com.firebaseui:firebase-ui-database:8.0.2' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + // Needed to fix a dependency conflict with FirebaseUI' implementation 'androidx.arch.core:core-runtime:2.1.0' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/database/build.gradle b/database/build.gradle index 07766d813c..1554d788ed 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/dynamiclinks/app/build.gradle b/dynamiclinks/app/build.gradle index 44ef9afd29..879444de1d 100644 --- a/dynamiclinks/app/build.gradle +++ b/dynamiclinks/app/build.gradle @@ -8,16 +8,19 @@ check.dependsOn 'assembleMainFlavorDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.deeplinks' - compileSdkVersion 33 + compileSdk 33 flavorDimensions "irrelevant" defaultConfig { applicationId "com.google.firebase.quickstart.deeplinks" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -42,6 +45,22 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -50,9 +69,10 @@ dependencies { implementation project(":internal:chooserx") implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Dynamic Links (Java) implementation 'com.google.firebase:firebase-dynamic-links' @@ -64,8 +84,18 @@ dependencies { // for Google Analytics. This is recommended, but not required. implementation 'com.google.firebase:firebase-analytics' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' + + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/dynamiclinks/app/src/main/AndroidManifest.xml b/dynamiclinks/app/src/main/AndroidManifest.xml index 615a7f5e9b..b22caa9f63 100644 --- a/dynamiclinks/app/src/main/AndroidManifest.xml +++ b/dynamiclinks/app/src/main/AndroidManifest.xml @@ -19,7 +19,6 @@ - @@ -28,7 +27,6 @@ android:host="example.com" android:scheme="https"/> - + + + + + + + + + diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/EntryChoiceActivity.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/EntryChoiceActivity.kt index bf71091a90..d2cc39708f 100644 --- a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/EntryChoiceActivity.kt +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/EntryChoiceActivity.kt @@ -15,7 +15,11 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { Choice( "Kotlin", "Run the Firebase Dynamic Links quickstart written in Kotlin.", - Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainActivity::class.java)) + Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainActivity::class.java)), + Choice( + "Compose", + "Run the Firebase Dynamic Links quickstart written in Compose.", + Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainComposeActivity::class.java)) ) } } diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/java/MainActivity.java b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/java/MainActivity.java index bde97fc687..3758392f62 100644 --- a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/java/MainActivity.java +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/java/MainActivity.java @@ -46,10 +46,8 @@ public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final String DEEP_LINK_URL = "https://example.com/deeplinks"; - // [START on_create] @Override protected void onCreate(Bundle savedInstanceState) { - // [START_EXCLUDE] super.onCreate(savedInstanceState); ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -89,9 +87,7 @@ public void onClick(View v) { buildShortLinkFromParams(deepLink, 0); } }); - // [END_EXCLUDE] - // [START get_deep_link] FirebaseDynamicLinks.getInstance() .getDynamicLink(getIntent()) .addOnSuccessListener(this, new OnSuccessListener() { @@ -109,7 +105,6 @@ public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) { // account. // ... - // [START_EXCLUDE] // Display deep link in the UI if (deepLink != null) { Snackbar.make(findViewById(android.R.id.content), @@ -119,7 +114,6 @@ public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) { } else { Log.d(TAG, "getDynamicLink: no link found"); } - // [END_EXCLUDE] } }) .addOnFailureListener(this, new OnFailureListener() { @@ -128,9 +122,7 @@ public void onFailure(@NonNull Exception e) { Log.w(TAG, "getDynamicLink:onFailure", e); } }); - // [END get_deep_link] } - // [END on_create] /** * Build a Firebase Dynamic Link. @@ -152,7 +144,6 @@ public Uri buildDeepLink(@NonNull Uri deepLink, int minVersion) { // * URI prefix (required) // * Android Parameters (required) // * Deep link - // [START build_dynamic_link] DynamicLink.Builder builder = FirebaseDynamicLinks.getInstance() .createDynamicLink() .setDomainUriPrefix(uriPrefix) @@ -163,7 +154,6 @@ public Uri buildDeepLink(@NonNull Uri deepLink, int minVersion) { // Build the dynamic link DynamicLink link = builder.buildDynamicLink(); - // [END build_dynamic_link] // Return the dynamic link as a URI return link.getUri(); diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt new file mode 100644 index 0000000000..f3031fd419 --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt @@ -0,0 +1,143 @@ +package com.google.firebase.quickstart.deeplinks.kotlin + +import android.content.Intent +import android.net.Uri +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.google.firebase.dynamiclinks.FirebaseDynamicLinks +import com.google.firebase.dynamiclinks.PendingDynamicLinkData +import com.google.firebase.dynamiclinks.ktx.androidParameters +import com.google.firebase.dynamiclinks.ktx.dynamicLink +import com.google.firebase.dynamiclinks.ktx.dynamicLinks +import com.google.firebase.dynamiclinks.ktx.shortLinkAsync +import com.google.firebase.ktx.Firebase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +class DynamicLinksViewModel( + private val dynamicLinks: FirebaseDynamicLinks +): ViewModel() { + + private val _deepLink = MutableStateFlow("") + val deepLink: StateFlow = _deepLink + + private val _shortLink = MutableStateFlow("") + val shortLink: StateFlow = _shortLink + + private val _validUriPrefix = MutableStateFlow(true) + val validUriPrefix: StateFlow = _validUriPrefix + + fun getDynamicLink(intent: Intent) { + viewModelScope.launch { + try { + val pendingDynamicLinkData: PendingDynamicLinkData = dynamicLinks + .getDynamicLink(intent) + .await() + + val deepLink: Uri? = pendingDynamicLinkData.link + + // Handle the deep link. For example, open the linked + // content, or apply promotional credit to the user's + // account. + // ... + + // Display deep link in the UI + if (deepLink != null) { + _deepLink.value = deepLink.toString() + } else { + Log.d(TAG, "getDynamicLink: no link found") + } + } catch (e: Exception) { + Log.w(TAG, "getDynamicLink:onFailure", e) + } + } + } + + + /** + * Build a Firebase Dynamic Link. + * https://firebase.google.com/docs/dynamic-links/android/create#create-a-dynamic-link-from-parameters + * + * @param deepLink the deep link your app will open. This link must be a valid URL and use the + * HTTP or HTTPS scheme. + * @param minVersion the `versionCode` of the minimum version of your app that can open + * the deep link. If the installed app is an older version, the user is taken + * to the Play store to upgrade the app. Pass 0 if you do not + * require a minimum version. + * @return a [Uri] representing a properly formed deep link. + */ +// @VisibleForTesting + fun buildDeepLink(uriPrefix: String, deepLink: Uri, minVersion: Int): Uri { + // Set dynamic link parameters: + // * URI prefix (required) + // * Android Parameters (required) + // * Deep link + // Build the dynamic link + val link = Firebase.dynamicLinks.dynamicLink { + domainUriPrefix = uriPrefix + androidParameters { + minimumVersion = minVersion + } + link = deepLink + } + + // Return the dynamic link as a URI + return link.uri + } + +// @VisibleForTesting + fun buildShortLinkFromParams(uriPrefix: String, deepLink: Uri, minVersion: Int) { + // Set dynamic link parameters: + // * URI prefix (required) + // * Android Parameters (required) + // * Deep link + + try { + viewModelScope.launch { + val shortDynamicLinks = Firebase.dynamicLinks.shortLinkAsync { + link = deepLink + domainUriPrefix = uriPrefix + androidParameters { + minimumVersion = minVersion + } + }.await() + + val shortLinks = shortDynamicLinks.shortLink + // val flowChartLink = shortDynamicLinks.previewLink + + _shortLink.value = shortLinks.toString() + } + } catch (e: Exception) { + Log.e(TAG, e.toString()) + } + } + + fun validateAppCode(uriPrefix: String) { + if (uriPrefix.contains("YOUR_APP")) { + _validUriPrefix.value = false + } + } + + companion object { + const val TAG = "DynamicLinksViewModel" + + // Used to inject this ViewModel's dependencies + // See also: https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras + ): T { + // Get Remote Config instance. + val dynamicLinks = Firebase.dynamicLinks + return DynamicLinksViewModel(dynamicLinks) as T + } + } + } +} \ No newline at end of file diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainActivity.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainActivity.kt index fb4e50ec79..33170baa4e 100644 --- a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainActivity.kt +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainActivity.kt @@ -3,143 +3,83 @@ package com.google.firebase.quickstart.deeplinks.kotlin import android.content.Intent import android.net.Uri import android.os.Bundle -import androidx.annotation.VisibleForTesting -import com.google.android.material.snackbar.Snackbar +import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import android.util.Log -import android.widget.TextView -import com.google.firebase.dynamiclinks.PendingDynamicLinkData -import com.google.firebase.dynamiclinks.ktx.androidParameters -import com.google.firebase.dynamiclinks.ktx.dynamicLink -import com.google.firebase.dynamiclinks.ktx.dynamicLinks -import com.google.firebase.dynamiclinks.ktx.shortLinkAsync -import com.google.firebase.dynamiclinks.ktx.component1 -import com.google.firebase.dynamiclinks.ktx.component2 -import com.google.firebase.ktx.Firebase +import androidx.lifecycle.lifecycleScope +import com.google.android.material.snackbar.Snackbar import com.google.firebase.quickstart.deeplinks.R import com.google.firebase.quickstart.deeplinks.databinding.ActivityMainBinding +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { - // [START on_create] + private val viewModel: DynamicLinksViewModel by viewModels { DynamicLinksViewModel.Factory } + override fun onCreate(savedInstanceState: Bundle?) { - // [START_EXCLUDE] super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val linkSendTextView = binding.linkViewSend val linkReceiveTextView = binding.linkViewReceive + val shortLinkTextView = binding.shortLinkViewSend + + val uriPrefix = getString(R.string.dynamic_links_uri_prefix) // Validate that the developer has set the app code. - validateAppCode() + viewModel.validateAppCode(uriPrefix) // Create a deep link and display it in the UI - val newDeepLink = buildDeepLink(Uri.parse(DEEP_LINK_URL), 0) + + val newDeepLink = viewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0) linkSendTextView.text = newDeepLink.toString() // Share button click listener binding.buttonShare.setOnClickListener { shareDeepLink(newDeepLink.toString()) } - // [END_EXCLUDE] binding.buttonShareShortLink.setOnClickListener { - val shortLinkTextView = findViewById(R.id.shortLinkViewSend); - val shortDynamicLink = shortLinkTextView.text; - shareDeepLink(shortDynamicLink.toString()); + val shortDynamicLink = shortLinkTextView.text + shareDeepLink(shortDynamicLink.toString()) } binding.buttonGenerateShortLink.setOnClickListener { - val deepLink = Uri.parse(DEEP_LINK_URL); - buildShortLinkFromParams(deepLink, 0); + val deepLink = Uri.parse(DEEP_LINK_URL) + viewModel.buildShortLinkFromParams(uriPrefix, deepLink, 0) } - // [START get_deep_link] - Firebase.dynamicLinks - .getDynamicLink(intent) - .addOnSuccessListener(this) { pendingDynamicLinkData: PendingDynamicLinkData? -> - // Get deep link from result (may be null if no link is found) - var deepLink: Uri? = null - if (pendingDynamicLinkData != null) { - deepLink = pendingDynamicLinkData.link - } - - // Handle the deep link. For example, open the linked - // content, or apply promotional credit to the user's - // account. - // ... - - // [START_EXCLUDE] - // Display deep link in the UI - if (deepLink != null) { - Snackbar.make(findViewById(android.R.id.content), - "Found deep link!", Snackbar.LENGTH_LONG).show() - - linkReceiveTextView.text = deepLink.toString() - } else { - Log.d(TAG, "getDynamicLink: no link found") - } - // [END_EXCLUDE] - } - .addOnFailureListener(this) { e -> Log.w(TAG, "getDynamicLink:onFailure", e) } - // [END get_deep_link] - } - // [END on_create] - - /** - * Build a Firebase Dynamic Link. - * https://firebase.google.com/docs/dynamic-links/android/create#create-a-dynamic-link-from-parameters - * - * @param deepLink the deep link your app will open. This link must be a valid URL and use the - * HTTP or HTTPS scheme. - * @param minVersion the `versionCode` of the minimum version of your app that can open - * the deep link. If the installed app is an older version, the user is taken - * to the Play store to upgrade the app. Pass 0 if you do not - * require a minimum version. - * @return a [Uri] representing a properly formed deep link. - */ - @VisibleForTesting - fun buildDeepLink(deepLink: Uri, minVersion: Int): Uri { - val uriPrefix = getString(R.string.dynamic_links_uri_prefix) + viewModel.getDynamicLink(intent) - // Set dynamic link parameters: - // * URI prefix (required) - // * Android Parameters (required) - // * Deep link - // [START build_dynamic_link] - // Build the dynamic link - val link = Firebase.dynamicLinks.dynamicLink { - domainUriPrefix = uriPrefix - androidParameters { - minimumVersion = minVersion + lifecycleScope.launch { + viewModel.deepLink.collect { deepLink -> + if(deepLink.isNotEmpty()){ + linkReceiveTextView.text = deepLink + Snackbar.make(findViewById(android.R.id.content), + "Found deep link!", Snackbar.LENGTH_LONG).show() + } } - link = deepLink } - // [END build_dynamic_link] - - // Return the dynamic link as a URI - return link.uri - } - @VisibleForTesting - fun buildShortLinkFromParams(deepLink: Uri, minVersion: Int) { - val uriPrefix = getString(R.string.dynamic_links_uri_prefix) + lifecycleScope.launch { + viewModel.shortLink.collect { shortLink -> + if(shortLink.isNotEmpty()){ + shortLinkTextView.text = shortLink + } + } + } - // Set dynamic link parameters: - // * URI prefix (required) - // * Android Parameters (required) - // * Deep link - Firebase.dynamicLinks.shortLinkAsync { - link = deepLink - domainUriPrefix = uriPrefix - androidParameters { - minimumVersion = minVersion + lifecycleScope.launch { + viewModel.validUriPrefix.collect { flag -> + if(!flag){ + AlertDialog.Builder(applicationContext) + .setTitle("Invalid Configuration") + .setMessage("Please set your Dynamic Links domain in app/build.gradle") + .setPositiveButton(android.R.string.ok, null) + .create().show() + } } - }.addOnSuccessListener { (shortLink, flowchartLink) -> - val shortLinkTextView = findViewById(R.id.shortLinkViewSend); - shortLinkTextView.text = shortLink.toString(); - }.addOnFailureListener(this) { e -> - Log.e(TAG, e.toString()); } + + } private fun shareDeepLink(deepLink: String) { @@ -151,17 +91,6 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } - private fun validateAppCode() { - val uriPrefix = getString(R.string.dynamic_links_uri_prefix) - if (uriPrefix.contains("YOUR_APP")) { - AlertDialog.Builder(this) - .setTitle("Invalid Configuration") - .setMessage("Please set your Dynamic Links domain in app/build.gradle") - .setPositiveButton(android.R.string.ok, null) - .create().show() - } - } - companion object { private const val TAG = "MainActivity" diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainComposeActivity.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainComposeActivity.kt new file mode 100644 index 0000000000..8dcc7e54cf --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainComposeActivity.kt @@ -0,0 +1,315 @@ +package com.google.firebase.quickstart.deeplinks.kotlin + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +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.material.AlertDialog +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.rememberScaffoldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.deeplinks.R +import com.google.firebase.quickstart.deeplinks.kotlin.ui.theme.DynamicLinksTheme +import kotlinx.coroutines.channels.Channel + +private const val DEEP_LINK_URL = "https://www.youtube.com/deeplinks" + +class MainComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + DynamicLinksTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + MainAppView() + } + } + } + } +} + +@Composable +fun MainAppView( + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + dynamicLinksViewModel: DynamicLinksViewModel = viewModel(factory = DynamicLinksViewModel.Factory) +) { + val context = LocalContext.current + val activity = context.findActivity() + val intent = activity?.intent + val uriPrefix = stringResource(R.string.dynamic_links_uri_prefix) + var newDeepLink by remember { mutableStateOf("") } + var openDialog by remember { mutableStateOf(false) } + + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + // Configure Firebase Dynamic Links when the screen is created + if (event == Lifecycle.Event.ON_CREATE) { + // Validate that the developer has set the app code. + dynamicLinksViewModel.validateAppCode(uriPrefix) + + newDeepLink = dynamicLinksViewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0).toString() + + intent?.let { + dynamicLinksViewModel.getDynamicLink(it) + } + } + } + + lifecycleOwner.lifecycle.addObserver(observer) + + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + + val snackbarHostState = remember { SnackbarHostState() } + val channel = remember { Channel(Channel.Factory.CONFLATED) } + val scaffoldState = rememberScaffoldState(snackbarHostState = snackbarHostState) + + // Checks if a valid uri has been updated, show only once + LaunchedEffect(key1 = true) { + dynamicLinksViewModel.validUriPrefix.collect { flag -> + if (!flag) { + openDialog = true + } + } + } + + // Checks if a deep link is used to open app + LaunchedEffect(key1 = channel) { + dynamicLinksViewModel.deepLink.collect { deepLink -> + if (deepLink.isNotEmpty()) { + snackbarHostState.showSnackbar("Found deep link!") + } + } + } + Scaffold( + scaffoldState = scaffoldState, + topBar = { + TopAppBar( + backgroundColor = colorResource(R.color.colorPrimary) + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.h6, + textAlign = TextAlign.Center, + modifier = Modifier.padding(8.dp), + color = Color.White + ) + } + }, + content = { it -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { + MainContent( + _openDialog = openDialog, + _linkReceiveTextView = dynamicLinksViewModel.deepLink.collectAsState(), + _shortLinkTextView = dynamicLinksViewModel.shortLink.collectAsState(), + newDeepLink = newDeepLink, + buildDeepLink = { + dynamicLinksViewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0).toString() + }, + buildShortLinkFromParams = { + dynamicLinksViewModel.buildShortLinkFromParams(uriPrefix, it, 0) + } + ) + } + } + ) +} + +@Composable +fun MainContent( + _openDialog: Boolean = false, + _linkReceiveTextView: State = mutableStateOf(""), + _shortLinkTextView: State = mutableStateOf(""), + newDeepLink: String = "", + buildDeepLink: () -> String = { "" }, + buildShortLinkFromParams: (Uri) -> Unit = {}, +){ + val context = LocalContext.current + var openDialog by remember { mutableStateOf(_openDialog) } + val linkReceiveTextView by _linkReceiveTextView + val shortLinkTextView by _shortLinkTextView + + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + ) { + if(openDialog) { + AlertDialog( + onDismissRequest = { openDialog = false }, + title = { Text("Invalid Configuration") }, + text = { Text("Please set your Dynamic Links domain in app/build.gradle") }, + confirmButton = { + Button(onClick = { + openDialog = false + }) { + Text(stringResource(android.R.string.ok)) + } + } + ) + } + + Image( + painter = painterResource(R.drawable.firebase_lockup_400), + contentDescription = "", + modifier = Modifier.fillMaxWidth(), + alignment = Alignment.Center + ) + + Text( + text = stringResource(R.string.title_receive), + fontSize = 20.sp, + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 8.dp) + ) + + Text( + text = linkReceiveTextView.ifEmpty { + stringResource(R.string.msg_no_deep_link) + }, + fontSize = 16.sp, + modifier = Modifier.padding(top = 8.dp) + ) + + Text( + text = stringResource(R.string.dynamic_link), + fontSize = 20.sp, + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 32.dp) + ) + + Text( + text = buildDeepLink().ifEmpty { "https://abc.xyz/foo" }, + fontSize = 16.sp, + modifier = Modifier.padding(top = 8.dp) + ) + + Button( + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)), + onClick = { + shareDeepLink(context, newDeepLink) + } + ) { + Text( + text = stringResource(R.string.share_dynamic_link).uppercase(), + color = Color.White, + ) + } + + Text( + text = stringResource(R.string.short_dynamic_link), + fontSize = 20.sp, + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 32.dp) + ) + + Text( + text = shortLinkTextView.ifEmpty { + "https://abc.xyz/foo" + }, + fontSize = 16.sp, + modifier = Modifier.padding(top = 8.dp) + ) + + Button( + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)), + onClick = { + val deepLink = Uri.parse(DEEP_LINK_URL) + buildShortLinkFromParams(deepLink) + } + ) { + Text( + text = stringResource(R.string.generate_short_link).uppercase(), + color = Color.White + ) + } + + Button( + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)), + onClick = { + shareDeepLink(context, shortLinkTextView) + } + ) { + Text( + text = stringResource(R.string.share_short_link).uppercase(), + color = Color.White + ) + } + } +} + +@Composable +@Preview(showBackground = true) +fun MainContentPreview(){ + DynamicLinksTheme { + MainContent() + } +} + +fun shareDeepLink(context: Context, deepLink: String) { + val intent = Intent(Intent.ACTION_SEND) + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_SUBJECT, "Firebase Deep Link") + intent.putExtra(Intent.EXTRA_TEXT, deepLink) + + context.startActivity(intent) +} + +fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} \ No newline at end of file diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Color.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Color.kt new file mode 100644 index 0000000000..b500a555a1 --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.google.firebase.quickstart.deeplinks.kotlin.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val FirebaseBlue = Color(0xFF0288D1) // copied from colors.xml +val FirebaseBannerBlue = Color(0xFF039BE5) // copied from colors.xml +val FirebaseOrange = Color(0xFFFFA000) // copied from colors.xml \ No newline at end of file diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Shape.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Shape.kt new file mode 100644 index 0000000000..b273cfb639 --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.google.firebase.quickstart.deeplinks.kotlin.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(16.dp), + medium = RoundedCornerShape(2.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Theme.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Theme.kt new file mode 100644 index 0000000000..f5aa458dd4 --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Theme.kt @@ -0,0 +1,48 @@ +package com.google.firebase.quickstart.deeplinks.kotlin.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple80, + primaryVariant = PurpleGrey80, + secondary = Pink80 +) + +private val LightColorPalette = lightColors( + primary = FirebaseBlue, + primaryVariant = FirebaseBannerBlue, + secondary = FirebaseOrange + + /* 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 DynamicLinksTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Type.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Type.kt new file mode 100644 index 0000000000..67f305a2fd --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.google.firebase.quickstart.deeplinks.kotlin.ui.theme + +import androidx.compose.material.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( + body1 = 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/dynamiclinks/build.gradle b/dynamiclinks/build.gradle index 07766d813c..1554d788ed 100644 --- a/dynamiclinks/build.gradle +++ b/dynamiclinks/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/dynamiclinks/gradle.properties b/dynamiclinks/gradle.properties index aac7c9b461..29b531a1d1 100644 --- a/dynamiclinks/gradle.properties +++ b/dynamiclinks/gradle.properties @@ -10,7 +10,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m - +android.useAndroidX=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/firestore/app/build.gradle b/firestore/app/build.gradle index 3595e1ea5a..3877cae5a1 100644 --- a/firestore/app/build.gradle +++ b/firestore/app/build.gradle @@ -8,16 +8,18 @@ plugins { android { namespace 'com.google.firebase.example.fireeats' testBuildType "release" - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.example.fireeats" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true - + vectorDrawables { + useSupportLibrary true + } vectorDrawables.useSupportLibrary true lintOptions { @@ -37,6 +39,22 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -84,6 +102,12 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.5.1' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + // Third-party libraries implementation 'me.zhanghai.android.materialratingbar:library:1.4.0' implementation 'com.github.bumptech.glide:glide:4.12.0' @@ -96,5 +120,8 @@ dependencies { androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation 'org.hamcrest:hamcrest-library:2.2' androidTestImplementation 'com.google.firebase:firebase-auth' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/firestore/build.gradle b/firestore/build.gradle index 40c39f9f83..c50a5d8982 100644 --- a/firestore/build.gradle +++ b/firestore/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3' } } diff --git a/functions/app/build.gradle b/functions/app/build.gradle index 8b6dd01bd4..ce9601ec52 100644 --- a/functions/app/build.gradle +++ b/functions/app/build.gradle @@ -8,15 +8,18 @@ android { namespace 'com.google.samples.quickstart.functions' // Changes the test build type for instrumented tests to "stage". testBuildType "release" - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.samples.quickstart.functions" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -31,6 +34,22 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -42,6 +61,7 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.5.4' implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation platform('com.google.firebase:firebase-bom:31.1.0') @@ -67,9 +87,18 @@ dependencies { // Google Play services implementation 'com.google.android.gms:play-services-auth:20.4.0' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:runner:1.5.1' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/functions/build.gradle b/functions/build.gradle index 07766d813c..1554d788ed 100644 --- a/functions/build.gradle +++ b/functions/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/functions/firebase.json b/functions/firebase.json index 0967ef424b..1bee05ed6d 100644 --- a/functions/firebase.json +++ b/functions/firebase.json @@ -1 +1,14 @@ -{} +{ + "functions": [ + { + "source": "functions", + "codebase": "default", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ] + } + ] +} diff --git a/inappmessaging/app/build.gradle b/inappmessaging/app/build.gradle index 4dbfb82832..00b6c2746a 100644 --- a/inappmessaging/app/build.gradle +++ b/inappmessaging/app/build.gradle @@ -6,15 +6,18 @@ plugins { android { namespace 'com.google.firebase.fiamquickstart' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.fiamquickstart" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -28,6 +31,22 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } lint { warning 'InvalidPackage' @@ -41,9 +60,10 @@ dependencies { implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // FIAM (Java) implementation 'com.google.firebase:firebase-inappmessaging-display' @@ -60,8 +80,17 @@ dependencies { implementation 'com.google.firebase:firebase-installations-ktx:17.1.0' - androidTestImplementation 'androidx.test:runner:1.5.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/inappmessaging/build.gradle b/inappmessaging/build.gradle index 9332ee6330..64c31555c4 100644 --- a/inappmessaging/build.gradle +++ b/inappmessaging/build.gradle @@ -10,7 +10,7 @@ buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.14' classpath 'com.android.tools.build:gradle:7.3.1' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index 1260588b71..9324342ec4 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -8,16 +8,18 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.fcm' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.quickstart.fcm" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true - + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -36,6 +38,22 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } lint { abortOnError false @@ -52,6 +70,7 @@ dependencies { // Required when asking for permission to post notifications (starting in Android 13) implementation 'androidx.activity:activity-ktx:1.6.1' implementation 'androidx.fragment:fragment-ktx:1.5.4' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'com.google.android.material:material:1.7.0' @@ -72,9 +91,18 @@ dependencies { implementation 'androidx.work:work-runtime:2.7.1' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + // Testing dependencies androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' androidTestImplementation 'androidx.test:runner:1.5.1' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.annotation:annotation:1.5.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/messaging/build.gradle b/messaging/build.gradle index 98de3b7f07..f9bdde250d 100644 --- a/messaging/build.gradle +++ b/messaging/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/perf/app/build.gradle b/perf/app/build.gradle index f6da70a13c..f17e3c18b3 100644 --- a/perf/app/build.gradle +++ b/perf/app/build.gradle @@ -9,14 +9,17 @@ check.dependsOn 'assembleDebugAndroidTest' android { namespace 'com.google.firebase.quickstart.perfmon' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.google.firebase.quickstart.perfmon" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -35,6 +38,22 @@ android { } buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } } @@ -43,7 +62,7 @@ dependencies { implementation project(":internal:chooserx") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Firebase Performance Monitoring (Java) implementation 'com.google.firebase:firebase-perf' @@ -57,6 +76,15 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.12.0' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/perf/build.gradle b/perf/build.gradle index 5b63840354..9b357abce3 100644 --- a/perf/build.gradle +++ b/perf/build.gradle @@ -10,7 +10,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.firebase:perf-plugin:1.4.2' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } } diff --git a/storage/app/build.gradle b/storage/app/build.gradle index 3e5a81a2d9..db63bbbc27 100644 --- a/storage/app/build.gradle +++ b/storage/app/build.gradle @@ -7,15 +7,19 @@ plugins { check.dependsOn 'assembleDebugAndroidTest' android { - compileSdkVersion 33 + namespace 'com.google.firebase.quickstart.firebasestorage' + compileSdk 33 defaultConfig { applicationId "com.google.firebase.quickstart.firebasestorage" - minSdkVersion 19 - targetSdkVersion 33 + minSdk 21 // minSdk would be 19 without compose + targetSdk 33 versionCode 1 versionName "1.0" multiDexEnabled true + vectorDrawables { + useSupportLibrary true + } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -28,8 +32,23 @@ android { buildFeatures { viewBinding = true + compose = true + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + composeOptions { + kotlinCompilerExtensionVersion '1.3.2' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } } - namespace 'com.google.firebase.quickstart.firebasestorage' } dependencies { @@ -37,7 +56,7 @@ dependencies { implementation project(":internal:chooserx") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation platform('com.google.firebase:firebase-bom:31.1.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') // Cloud Storage for Firebase (Java) implementation 'com.google.firebase:firebase-storage' @@ -52,10 +71,20 @@ dependencies { implementation 'com.google.firebase:firebase-auth-ktx' implementation 'androidx.activity:activity-ktx:1.6.1' + // Jetpack Compose + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.activity:activity-compose:1.5.1' + implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.7.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test:runner:1.5.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" } diff --git a/storage/build.gradle b/storage/build.gradle index 8c2f8a5842..cdf5c6bb90 100644 --- a/storage/build.gradle +++ b/storage/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' } }