From 1e634553e181eb410524c5a6471e102f3f193114 Mon Sep 17 00:00:00 2001 From: argzdev Date: Fri, 14 Apr 2023 00:09:26 +0800 Subject: [PATCH 1/6] - Removed business logic from MainActivity - Added DynamicLinksViewModel for business logic - Added MainComposeActivity for the jetpack compose UI --- dynamiclinks/app/build.gradle | 1 + dynamiclinks/app/src/main/AndroidManifest.xml | 12 + .../deeplinks/EntryChoiceActivity.kt | 6 +- .../deeplinks/kotlin/DynamicLinksViewModel.kt | 133 +++++++++ .../deeplinks/kotlin/MainActivity.kt | 150 +++------- .../deeplinks/kotlin/MainComposeActivity.kt | 261 ++++++++++++++++++ .../deeplinks/kotlin/ui/theme/Color.kt | 11 + .../deeplinks/kotlin/ui/theme/Shape.kt | 11 + .../deeplinks/kotlin/ui/theme/Theme.kt | 48 ++++ .../deeplinks/kotlin/ui/theme/Type.kt | 34 +++ dynamiclinks/build.gradle | 3 + dynamiclinks/gradle.properties | 2 +- 12 files changed, 562 insertions(+), 110 deletions(-) create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainComposeActivity.kt create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Color.kt create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Shape.kt create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Theme.kt create mode 100644 dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/ui/theme/Type.kt diff --git a/dynamiclinks/app/build.gradle b/dynamiclinks/app/build.gradle index 437e8a3c47..879444de1d 100644 --- a/dynamiclinks/app/build.gradle +++ b/dynamiclinks/app/build.gradle @@ -89,6 +89,7 @@ dependencies { 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' diff --git a/dynamiclinks/app/src/main/AndroidManifest.xml b/dynamiclinks/app/src/main/AndroidManifest.xml index 615a7f5e9b..849f51c11d 100644 --- a/dynamiclinks/app/src/main/AndroidManifest.xml +++ b/dynamiclinks/app/src/main/AndroidManifest.xml @@ -42,6 +42,18 @@ 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..df002c2941 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/kotlin/DynamicLinksViewModel.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt new file mode 100644 index 0000000000..ae1cb649b7 --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/DynamicLinksViewModel.kt @@ -0,0 +1,133 @@ +package com.google.firebase.quickstart.deeplinks.kotlin + +import android.content.Intent +import android.net.Uri +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras +import com.google.firebase.dynamiclinks.FirebaseDynamicLinks +import com.google.firebase.dynamiclinks.PendingDynamicLinkData +import com.google.firebase.dynamiclinks.ktx.* +import com.google.firebase.ktx.Firebase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +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) { + dynamicLinks + .getDynamicLink(intent) + .addOnSuccessListener { 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) { + _deepLink.value = deepLink.toString() + } else { + Log.d(TAG, "getDynamicLink: no link found") + } + // [END_EXCLUDE] + } + .addOnFailureListener { 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 + // [START build_dynamic_link] + // Build the dynamic link + val link = Firebase.dynamicLinks.dynamicLink { + domainUriPrefix = uriPrefix + androidParameters { + minimumVersion = minVersion + } + link = deepLink + } + // [END build_dynamic_link] + + // 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 + Firebase.dynamicLinks.shortLinkAsync { + link = deepLink + domainUriPrefix = uriPrefix + androidParameters { + minimumVersion = minVersion + } + }.addOnSuccessListener { (shortLink, flowchartLink) -> + _shortLink.value = shortLink.toString() + }.addOnFailureListener { e -> + 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..31e5e3da6c 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,24 +3,18 @@ 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() { + private val viewModel: DynamicLinksViewModel by viewModels { DynamicLinksViewModel.Factory } + // [START on_create] override fun onCreate(savedInstanceState: Bundle?) { // [START_EXCLUDE] @@ -30,12 +24,15 @@ class MainActivity : AppCompatActivity() { 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 @@ -43,104 +40,52 @@ class MainActivity : AppCompatActivity() { // [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) } + viewModel.getDynamicLink(intent) // [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) - // 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()); } + + } + // [END on_create] private fun shareDeepLink(deepLink: String) { val intent = Intent(Intent.ACTION_SEND) @@ -151,17 +96,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..bda5032bcb --- /dev/null +++ b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainComposeActivity.kt @@ -0,0 +1,261 @@ +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.* +import androidx.compose.runtime.* +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" +private const val TAG = "MainComposeActivity" + +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) + + // This will only run once and will not be triggered by recomposition + LaunchedEffect(key1 = true) { + dynamicLinksViewModel.validUriPrefix.collect { flag -> + if (!flag) { + openDialog = true + } + } + } + + // This will only run once and will not be triggered by recomposition + 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 + .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 + ) + + val linkReceiveTextView by dynamicLinksViewModel.deepLink.collectAsState() + val shortLinkTextView by dynamicLinksViewModel.shortLink.collectAsState() + + 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 = dynamicLinksViewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0).toString(), + 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) + dynamicLinksViewModel.buildShortLinkFromParams(uriPrefix, deepLink, 0) + } + ) { + 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 + ) + } + } + } + ) +} + +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 1554d788ed..5ee174d2f0 100644 --- a/dynamiclinks/build.gradle +++ b/dynamiclinks/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() 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 From 53827bc1052402b283f88425860c0f3dcb957fa5 Mon Sep 17 00:00:00 2001 From: argzdev Date: Fri, 14 Apr 2023 00:24:42 +0800 Subject: [PATCH 2/6] Add spacing. Add build_dynamic_link comment for test script --- .../firebase/quickstart/deeplinks/EntryChoiceActivity.kt | 8 ++++---- .../firebase/quickstart/deeplinks/kotlin/MainActivity.kt | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) 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 df002c2941..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 @@ -16,10 +16,10 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() { "Kotlin", "Run the Firebase Dynamic Links quickstart written in Kotlin.", 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)) + 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/kotlin/MainActivity.kt b/dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/kotlin/MainActivity.kt index 31e5e3da6c..75670b890e 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 @@ -32,7 +32,10 @@ class MainActivity : AppCompatActivity() { viewModel.validateAppCode(uriPrefix) // Create a deep link and display it in the UI + + // [START build_dynamic_link] val newDeepLink = viewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0) + // [END build_dynamic_link] linkSendTextView.text = newDeepLink.toString() // Share button click listener From 968bb618a4a2f240db9e06ff18eb4d0ceb6ed4ea Mon Sep 17 00:00:00 2001 From: argzdev Date: Fri, 14 Apr 2023 00:40:38 +0800 Subject: [PATCH 3/6] Removed all [START] & [END] comments --- dynamiclinks/app/src/main/AndroidManifest.xml | 2 -- .../quickstart/deeplinks/java/MainActivity.java | 10 ---------- .../deeplinks/kotlin/DynamicLinksViewModel.kt | 4 ---- .../quickstart/deeplinks/kotlin/MainActivity.kt | 8 -------- .../quickstart/deeplinks/kotlin/MainComposeActivity.kt | 6 ++---- 5 files changed, 2 insertions(+), 28 deletions(-) diff --git a/dynamiclinks/app/src/main/AndroidManifest.xml b/dynamiclinks/app/src/main/AndroidManifest.xml index 849f51c11d..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"/> - () { @@ -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 index ae1cb649b7..93e5f30b00 100644 --- 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 @@ -42,14 +42,12 @@ class DynamicLinksViewModel( // account. // ... - // [START_EXCLUDE] // Display deep link in the UI if (deepLink != null) { _deepLink.value = deepLink.toString() } else { Log.d(TAG, "getDynamicLink: no link found") } - // [END_EXCLUDE] } .addOnFailureListener { e: Exception -> Log.w(TAG, "getDynamicLink:onFailure", e) } } @@ -73,7 +71,6 @@ class DynamicLinksViewModel( // * URI prefix (required) // * Android Parameters (required) // * Deep link - // [START build_dynamic_link] // Build the dynamic link val link = Firebase.dynamicLinks.dynamicLink { domainUriPrefix = uriPrefix @@ -82,7 +79,6 @@ class DynamicLinksViewModel( } link = deepLink } - // [END build_dynamic_link] // Return the dynamic link as a URI return link.uri 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 75670b890e..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 @@ -15,9 +15,7 @@ import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { private val viewModel: DynamicLinksViewModel by viewModels { DynamicLinksViewModel.Factory } - // [START on_create] override fun onCreate(savedInstanceState: Bundle?) { - // [START_EXCLUDE] super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -33,14 +31,11 @@ class MainActivity : AppCompatActivity() { // Create a deep link and display it in the UI - // [START build_dynamic_link] val newDeepLink = viewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0) - // [END build_dynamic_link] linkSendTextView.text = newDeepLink.toString() // Share button click listener binding.buttonShare.setOnClickListener { shareDeepLink(newDeepLink.toString()) } - // [END_EXCLUDE] binding.buttonShareShortLink.setOnClickListener { val shortDynamicLink = shortLinkTextView.text @@ -52,9 +47,7 @@ class MainActivity : AppCompatActivity() { viewModel.buildShortLinkFromParams(uriPrefix, deepLink, 0) } - // [START get_deep_link] viewModel.getDynamicLink(intent) - // [END get_deep_link] lifecycleScope.launch { viewModel.deepLink.collect { deepLink -> @@ -88,7 +81,6 @@ class MainActivity : AppCompatActivity() { } - // [END on_create] private fun shareDeepLink(deepLink: String) { val intent = Intent(Intent.ACTION_SEND) 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 index bda5032bcb..e2f3b6fa2e 100644 --- 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 @@ -24,7 +24,6 @@ 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 @@ -36,7 +35,6 @@ import com.google.firebase.quickstart.deeplinks.kotlin.ui.theme.DynamicLinksThem import kotlinx.coroutines.channels.Channel private const val DEEP_LINK_URL = "https://www.youtube.com/deeplinks" -private const val TAG = "MainComposeActivity" class MainComposeActivity : ComponentActivity() { @@ -94,7 +92,7 @@ fun MainAppView( val channel = remember { Channel(Channel.Factory.CONFLATED) } val scaffoldState = rememberScaffoldState(snackbarHostState = snackbarHostState) - // This will only run once and will not be triggered by recomposition + // Checks if a valid uri has been updated, show only once LaunchedEffect(key1 = true) { dynamicLinksViewModel.validUriPrefix.collect { flag -> if (!flag) { @@ -103,7 +101,7 @@ fun MainAppView( } } - // This will only run once and will not be triggered by recomposition + // Checks if a deep link is used to open app LaunchedEffect(key1 = channel) { dynamicLinksViewModel.deepLink.collect { deepLink -> if (deepLink.isNotEmpty()) { From 57b0e0aab541718e1870c7f672f43e81797b0da2 Mon Sep 17 00:00:00 2001 From: argzdev Date: Fri, 14 Apr 2023 01:16:32 +0800 Subject: [PATCH 4/6] Separated variables from MainAppView and created MainContent. To generate Preview of the content. --- .../deeplinks/kotlin/MainComposeActivity.kt | 238 ++++++++++-------- 1 file changed, 136 insertions(+), 102 deletions(-) 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 index e2f3b6fa2e..610b00065f 100644 --- 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 @@ -24,6 +24,7 @@ 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 @@ -125,122 +126,155 @@ fun MainAppView( } }, content = { it - 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)) - } - } - ) - } + 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) + } - Image( - painter = painterResource(R.drawable.firebase_lockup_400), - contentDescription = "", - modifier = Modifier.fillMaxWidth(), - alignment = Alignment.Center ) + } + ) +} - val linkReceiveTextView by dynamicLinksViewModel.deepLink.collectAsState() - val shortLinkTextView by dynamicLinksViewModel.shortLink.collectAsState() +@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 - Text( - text = stringResource(R.string.title_receive), - fontSize = 20.sp, - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(top = 8.dp) - ) + 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)) + } + } + ) + } - Text( - text = linkReceiveTextView.ifEmpty { - stringResource(R.string.msg_no_deep_link) - }, - fontSize = 16.sp, - modifier = Modifier.padding(top = 8.dp) - ) + Image( + painter = painterResource(R.drawable.firebase_lockup_400), + contentDescription = "", + modifier = Modifier.fillMaxWidth(), + alignment = Alignment.Center + ) - Text( - text = stringResource(R.string.dynamic_link), - fontSize = 20.sp, - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(top = 32.dp) - ) + Text( + text = stringResource(R.string.title_receive), + fontSize = 20.sp, + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 8.dp) + ) - Text( - text = dynamicLinksViewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0).toString(), - fontSize = 16.sp, - 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) + ) - 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.dynamic_link), + fontSize = 20.sp, + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 32.dp) + ) - Text( - text = stringResource(R.string.short_dynamic_link), - fontSize = 20.sp, - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(top = 32.dp) - ) + Text( + text = buildDeepLink(), + fontSize = 16.sp, + modifier = Modifier.padding(top = 8.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 = { + shareDeepLink(context, newDeepLink) + } + ) { + Text( + text = stringResource(R.string.share_dynamic_link).uppercase(), + color = Color.White, + ) + } - Button( - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)), - onClick = { - val deepLink = Uri.parse(DEEP_LINK_URL) - dynamicLinksViewModel.buildShortLinkFromParams(uriPrefix, deepLink, 0) - } - ) { - Text( - text = stringResource(R.string.generate_short_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) + ) - 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 - ) - } + 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) { From 413b6f2a55e1523b03c600346f0ab713836af1a5 Mon Sep 17 00:00:00 2001 From: argzdev Date: Tue, 23 May 2023 03:31:53 +0800 Subject: [PATCH 5/6] - Removed `compose_version` in build.gradle since this exists in the parent directory path. - Converted methods `buildShortLinkFromParams` and `buildDeepLink` to use coroutines - Added Column to use paddingValues of `it` --- .../deeplinks/kotlin/DynamicLinksViewModel.kt | 41 +++++++++++++------ .../deeplinks/kotlin/MainComposeActivity.kt | 13 ++++-- dynamiclinks/build.gradle | 3 -- 3 files changed, 37 insertions(+), 20 deletions(-) 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 index 93e5f30b00..9e92ae5c61 100644 --- 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 @@ -6,6 +6,7 @@ import android.util.Log import androidx.annotation.VisibleForTesting 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 @@ -13,6 +14,8 @@ import com.google.firebase.dynamiclinks.ktx.* 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 @@ -28,10 +31,12 @@ class DynamicLinksViewModel( val validUriPrefix: StateFlow = _validUriPrefix fun getDynamicLink(intent: Intent) { - dynamicLinks - .getDynamicLink(intent) - .addOnSuccessListener { pendingDynamicLinkData: PendingDynamicLinkData? -> - // Get deep link from result (may be null if no link is found) + viewModelScope.launch { + try { + val pendingDynamicLinkData: PendingDynamicLinkData = dynamicLinks + .getDynamicLink(intent) + .await() + var deepLink: Uri? = null if (pendingDynamicLinkData != null) { deepLink = pendingDynamicLinkData.link @@ -48,8 +53,10 @@ class DynamicLinksViewModel( } else { Log.d(TAG, "getDynamicLink: no link found") } + } catch (e: Exception) { + Log.w(TAG, "getDynamicLink:onFailure", e) } - .addOnFailureListener { e: Exception -> Log.w(TAG, "getDynamicLink:onFailure", e) } + } } @@ -90,15 +97,23 @@ class DynamicLinksViewModel( // * URI prefix (required) // * Android Parameters (required) // * Deep link - Firebase.dynamicLinks.shortLinkAsync { - link = deepLink - domainUriPrefix = uriPrefix - androidParameters { - minimumVersion = minVersion + + 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() } - }.addOnSuccessListener { (shortLink, flowchartLink) -> - _shortLink.value = shortLink.toString() - }.addOnFailureListener { e -> + } catch (e: Exception) { Log.e(TAG, e.toString()) } } 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 index 610b00065f..8bf73301ce 100644 --- 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 @@ -59,7 +59,7 @@ class MainComposeActivity : ComponentActivity() { fun MainAppView( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, dynamicLinksViewModel: DynamicLinksViewModel = viewModel(factory = DynamicLinksViewModel.Factory) -){ +) { val context = LocalContext.current val activity = context.findActivity() val intent = activity?.intent @@ -125,7 +125,12 @@ fun MainAppView( ) } }, - content = { it + content = { it -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(it) + ) { MainContent( openDialog = openDialog, linkReceiveTextView = dynamicLinksViewModel.deepLink.collectAsState(), @@ -137,8 +142,8 @@ fun MainAppView( buildShortLinkFromParams = { dynamicLinksViewModel.buildShortLinkFromParams(uriPrefix, it, 0) } - ) + } } ) } @@ -207,7 +212,7 @@ fun MainContent( ) Text( - text = buildDeepLink(), + text = buildDeepLink().ifEmpty { "https://abc.xyz/foo" }, fontSize = 16.sp, modifier = Modifier.padding(top = 8.dp) ) diff --git a/dynamiclinks/build.gradle b/dynamiclinks/build.gradle index 5ee174d2f0..1554d788ed 100644 --- a/dynamiclinks/build.gradle +++ b/dynamiclinks/build.gradle @@ -1,9 +1,6 @@ // 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() From 2c593ba3735bed91c715cd398c6041e4ead31cab Mon Sep 17 00:00:00 2001 From: argzdev Date: Tue, 23 May 2023 18:24:36 +0800 Subject: [PATCH 6/6] - No wildcard imports - Removed @VisibleForTesting --- .../deeplinks/kotlin/DynamicLinksViewModel.kt | 17 ++++---- .../deeplinks/kotlin/MainComposeActivity.kt | 39 +++++++++++++------ 2 files changed, 36 insertions(+), 20 deletions(-) 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 index 9e92ae5c61..f3031fd419 100644 --- 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 @@ -3,14 +3,16 @@ package com.google.firebase.quickstart.deeplinks.kotlin import android.content.Intent import android.net.Uri import android.util.Log -import androidx.annotation.VisibleForTesting 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.* +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 @@ -37,10 +39,7 @@ class DynamicLinksViewModel( .getDynamicLink(intent) .await() - var deepLink: Uri? = null - if (pendingDynamicLinkData != null) { - deepLink = pendingDynamicLinkData.link - } + val deepLink: Uri? = pendingDynamicLinkData.link // Handle the deep link. For example, open the linked // content, or apply promotional credit to the user's @@ -72,7 +71,7 @@ class DynamicLinksViewModel( * require a minimum version. * @return a [Uri] representing a properly formed deep link. */ - @VisibleForTesting +// @VisibleForTesting fun buildDeepLink(uriPrefix: String, deepLink: Uri, minVersion: Int): Uri { // Set dynamic link parameters: // * URI prefix (required) @@ -91,7 +90,7 @@ class DynamicLinksViewModel( return link.uri } - @VisibleForTesting +// @VisibleForTesting fun buildShortLinkFromParams(uriPrefix: String, deepLink: Uri, minVersion: Int) { // Set dynamic link parameters: // * URI prefix (required) @@ -109,7 +108,7 @@ class DynamicLinksViewModel( }.await() val shortLinks = shortDynamicLinks.shortLink - val flowChartLink = shortDynamicLinks.previewLink + // val flowChartLink = shortDynamicLinks.previewLink _shortLink.value = shortLinks.toString() } 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 index 8bf73301ce..8dcc7e54cf 100644 --- 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 @@ -13,8 +13,25 @@ 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.* -import androidx.compose.runtime.* +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 @@ -132,9 +149,9 @@ fun MainAppView( .padding(it) ) { MainContent( - openDialog = openDialog, - linkReceiveTextView = dynamicLinksViewModel.deepLink.collectAsState(), - shortLinkTextView = dynamicLinksViewModel.shortLink.collectAsState(), + _openDialog = openDialog, + _linkReceiveTextView = dynamicLinksViewModel.deepLink.collectAsState(), + _shortLinkTextView = dynamicLinksViewModel.shortLink.collectAsState(), newDeepLink = newDeepLink, buildDeepLink = { dynamicLinksViewModel.buildDeepLink(uriPrefix, Uri.parse(DEEP_LINK_URL), 0).toString() @@ -150,17 +167,17 @@ fun MainAppView( @Composable fun MainContent( - openDialog: Boolean = false, - linkReceiveTextView: State = mutableStateOf(""), - shortLinkTextView: State = mutableStateOf(""), + _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 + var openDialog by remember { mutableStateOf(_openDialog) } + val linkReceiveTextView by _linkReceiveTextView + val shortLinkTextView by _shortLinkTextView Column( modifier = Modifier