Skip to content

Commit

Permalink
Add ReEngage prompt + sample
Browse files Browse the repository at this point in the history
  • Loading branch information
luizgrp committed Feb 23, 2024
1 parent 981d8ac commit fb00827
Show file tree
Hide file tree
Showing 18 changed files with 878 additions and 17 deletions.
12 changes: 11 additions & 1 deletion datalayer/phone-ui/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package com.google.android.horologist.datalayer.phone.ui {

@com.google.android.horologist.annotations.ExperimentalHorologistApi public final class PhoneUiDataLayerHelper {
ctor public PhoneUiDataLayerHelper();
method public android.content.Intent getInstallPromptIntent(android.content.Context context, String appPackageName, @DrawableRes int image, String topMessage, String bottomMessage);
method public android.content.Intent getInstallAppPromptIntent(android.content.Context context, String appPackageName, @DrawableRes int image, String topMessage, String bottomMessage);
method public android.content.Intent getReEngagePromptIntent(android.content.Context context, String nodeId, @DrawableRes int image, String topMessage, String bottomMessage, optional String? positiveButtonLabel, optional String? negativeButtonLabel);
method public void showInstallAppPrompt(android.app.Activity activity, String appPackageName, @DrawableRes int image, String topMessage, String bottomMessage, optional int requestCode);
method public void showReEngagePrompt(android.app.Activity activity, String nodeId, @DrawableRes int image, String topMessage, String bottomMessage, optional int requestCode);
}

}
Expand All @@ -25,3 +27,11 @@ package com.google.android.horologist.datalayer.phone.ui.prompt.installapp {

}

package com.google.android.horologist.datalayer.phone.ui.prompt.reengage {

public final class ReEngageBottomSheetKt {
method @androidx.compose.runtime.Composable public static void ReEngageBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit>? image, String topMessage, String bottomMessage, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> onConfirmation, optional androidx.compose.ui.Modifier modifier, optional String? positiveButtonLabel, optional String? negativeButtonLabel, optional androidx.compose.material3.SheetState sheetState);
}

}

3 changes: 2 additions & 1 deletion datalayer/phone-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ android {
compileSdk = 34

defaultConfig {
minSdk = 21
minSdk = 23

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -102,6 +102,7 @@ dependencies {
api(libs.compose.ui)
api(projects.annotations)

implementation(projects.datalayer.phone)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.corektx)
Expand Down
6 changes: 6 additions & 0 deletions datalayer/phone-ui/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
android:excludeFromRecents="true"
android:exported="false"
android:theme="@style/HorologistTheme.Transparent" />

<activity
android:name=".prompt.reengage.ReEngageBottomSheetActivity"
android:excludeFromRecents="true"
android:exported="false"
android:theme="@style/HorologistTheme.Transparent" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.datalayer.phone.ui.prompt.installapp.InstallAppBottomSheetActivity
import com.google.android.horologist.datalayer.phone.ui.prompt.reengage.ReEngageBottomSheetActivity

private const val NO_RESULT_REQUESTED_REQUEST_CODE = -1

Expand All @@ -48,7 +49,7 @@ public class PhoneUiDataLayerHelper {
bottomMessage: String,
requestCode: Int = NO_RESULT_REQUESTED_REQUEST_CODE,
) {
val intent = getInstallPromptIntent(
val intent = getInstallAppPromptIntent(
context = activity,
appPackageName = appPackageName,
image = image,
Expand All @@ -62,7 +63,7 @@ public class PhoneUiDataLayerHelper {
}

/**
* Returns the [Intent] to display an install prompt to the user.
* Returns the [Intent] to display an install app prompt to the user.
*
* This can be used in Compose with [rememberLauncherForActivityResult] and
* [ActivityResultLauncher.launch]:
Expand All @@ -76,7 +77,7 @@ public class PhoneUiDataLayerHelper {
* }
* }
*
* launcher.launch(getInstallPromptIntent(/*params*/))
* launcher.launch(getInstallAppPromptIntent(/*params*/))
* ```
*
* It can also be used directly in an [ComponentActivity] with
Expand All @@ -90,10 +91,10 @@ public class PhoneUiDataLayerHelper {
* }
* }
*
* launcher.launch(getInstallPromptIntent(/*params*/))
* launcher.launch(getInstallAppPromptIntent(/*params*/))
* ```
*/
public fun getInstallPromptIntent(
public fun getInstallAppPromptIntent(
context: Context,
appPackageName: String,
@DrawableRes image: Int,
Expand All @@ -106,4 +107,81 @@ public class PhoneUiDataLayerHelper {
topMessage = topMessage,
bottomMessage = bottomMessage,
)

/**
* Display a re-engage prompt to the user.
*
* Use [requestCode] as an option to check in [Activity.onActivityResult] if the prompt was
* dismissed ([Activity.RESULT_CANCELED]).
*/
public fun showReEngagePrompt(
activity: Activity,
nodeId: String,
@DrawableRes image: Int,
topMessage: String,
bottomMessage: String,
requestCode: Int = NO_RESULT_REQUESTED_REQUEST_CODE,
) {
val intent = getReEngagePromptIntent(
context = activity,
nodeId = nodeId,
image = image,
topMessage = topMessage,
bottomMessage = bottomMessage,
)
activity.startActivityForResult(
intent,
requestCode,
)
}

/**
* Returns the [Intent] to display a re-engage prompt to the user.
*
* This can be used in Compose with [rememberLauncherForActivityResult] and
* [ActivityResultLauncher.launch]:
*
* ```
* val launcher = rememberLauncherForActivityResult(
* ActivityResultContracts.StartActivityForResult()
* ) { result ->
* if (result.resultCode == RESULT_OK) {
* // user pushed try!
* }
* }
*
* launcher.launch(getReEngagePromptIntent(/*params*/))
* ```
*
* It can also be used directly in an [ComponentActivity] with
* [ComponentActivity.registerForActivityResult]:
* ```
* val launcher = registerForActivityResult(
* ActivityResultContracts.StartActivityForResult()
* ) { result ->
* if (result.resultCode == RESULT_OK) {
* // user pushed try!
* }
* }
*
* launcher.launch(getReEngagePromptIntent(/*params*/))
* ```
*/
public fun getReEngagePromptIntent(
context: Context,
nodeId: String,
@DrawableRes image: Int,
topMessage: String,
bottomMessage: String,
positiveButtonLabel: String? = null,
negativeButtonLabel: String? = null,
): Intent = ReEngageBottomSheetActivity.getIntent(
context = context,
nodeId = nodeId,
image = image,
topMessage = topMessage,
bottomMessage = bottomMessage,
positiveButtonLabel = positiveButtonLabel,
negativeButtonLabel = negativeButtonLabel,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.datalayer.phone.ui.di

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob

internal class CoroutineAppScope private constructor() {
companion object {

@Volatile
private var instance: CoroutineScope? = null

fun getInstance() =
instance ?: synchronized(this) {
instance ?: CoroutineScope(SupervisorJob() + Dispatchers.Default).also {
instance = it
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal class InstallAppBottomSheetActivity : ComponentActivity() {

setContent {
Surface {
val installAppBottomSheetState = rememberModalBottomSheetState()
val bottomSheetState = rememberModalBottomSheetState()
val coroutineScope = rememberCoroutineScope()

val image: (@Composable () -> Unit)? = imageResId.takeIf { it != NO_IMAGE }?.let {
Expand All @@ -73,7 +73,7 @@ internal class InstallAppBottomSheetActivity : ComponentActivity() {
setResult(RESULT_CANCELED)
coroutineScope.launch {
try {
installAppBottomSheetState.hide()
bottomSheetState.hide()
} finally {
finishWithoutAnimation()
}
Expand All @@ -85,7 +85,7 @@ internal class InstallAppBottomSheetActivity : ComponentActivity() {
setResult(RESULT_OK)
finishWithoutAnimation()
},
sheetState = installAppBottomSheetState,
sheetState = bottomSheetState,
)
}
}
Expand Down
Loading

0 comments on commit fb00827

Please sign in to comment.