Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions example/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android {
applicationId "com.mindbox.example"
minSdk 21
targetSdk 35
versionCode 8
versionCode 14
versionName "2.15.2"
multiDexEnabled true

Expand All @@ -46,11 +46,13 @@ android {
kotlinOptions {
jvmTarget = '17'
}
viewBinding {
enabled = true
}
buildFeatures {
buildConfig = true
compose = true
}
composeOptions {
// Compose compiler compatible with Kotlin 1.9.22
kotlinCompilerExtensionVersion = '1.5.10'
}
signingConfigs {
debug {
Expand Down Expand Up @@ -95,7 +97,17 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

implementation platform('androidx.compose:compose-bom:2024.06.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.foundation:foundation'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material-icons-extended'
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.8.2'
// Coil for async image loading in Compose
implementation 'io.coil-kt:coil-compose:2.6.0'

// Push services
implementation platform('com.google.firebase:firebase-bom:32.7.1')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,52 @@ package com.mindbox.example

import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import cloud.mindbox.mobile_sdk.Mindbox
import com.mindbox.example.databinding.ActivityTrasitionByPushBinding

import com.mindbox.example.ui.SecondActivityScreen
import com.mindbox.example.ui.theme.MindboxTheme

class ActivityTransitionByPush : AppCompatActivity() {
private var _binding: ActivityTrasitionByPushBinding? = null
private val binding: ActivityTrasitionByPushBinding
get() = _binding!!

private var pushUrl by mutableStateOf("")
private var pushPayload by mutableStateOf("")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityTrasitionByPushBinding.inflate(layoutInflater)
setContentView(binding.root)

//Get data from push after click on push or button in push
processMindboxIntent(intent, this)?.let { (url, payload) ->
binding.tvPushUrlResultSecondActivity.text = url
binding.tvPushPayloadResultSecondActivity.text = payload
// Get data from push after click on push or button in push
applyPushIntent(intent)

setContent {
MindboxTheme {
SecondActivityScreen(
pushUrl = pushUrl,
pushPayload = pushPayload,
triggerUrl = SECOND_ACTIVITY_PUSH_URL,
darkTheme = isSystemInDarkTheme(),
onBack = { finish() },
)
}
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)

//Get data from push after click on push or button in push
processMindboxIntent(intent, this)?.let { (url, payload) ->
binding.tvPushUrlResultSecondActivity.text = url
binding.tvPushPayloadResultSecondActivity.text = payload
}
//https://developers.mindbox.ru/docs/android-app-start-tracking
// Get data from push after click on push or button in push
applyPushIntent(intent)
// https://developers.mindbox.ru/docs/android-app-start-tracking
Mindbox.onNewIntent(intent)
}

override fun onDestroy() {
super.onDestroy()
_binding = null
private fun applyPushIntent(intent: Intent?) {
processMindboxIntent(intent, this)?.let { (url, payload) ->
pushUrl = url.orEmpty()
pushPayload = payload.orEmpty()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ object CustomInAppCallback : InAppCallback {
)
Log.d(Utils.TAG, "onInAppDismissed id=$id")
}
}
}

This file was deleted.

187 changes: 128 additions & 59 deletions example/app/src/main/java/com/mindbox/example/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,78 +1,118 @@
package com.mindbox.example

import android.Manifest
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import cloud.mindbox.mobile_sdk.Mindbox
import com.mindbox.example.databinding.ActivityMainBinding
import com.mindbox.example.ui.MainScreen
import com.mindbox.example.ui.SdkInfoState
import com.mindbox.example.ui.theme.MindboxTheme

class MainActivity : AppCompatActivity() {

private var _binding: ActivityMainBinding? = null
private val binding: ActivityMainBinding
get() = _binding!!
private var sdkInfo by mutableStateOf(SdkInfoState())
private var showInAppSheet by mutableStateOf(false)
private var showNavFragment by mutableStateOf(false)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

showSdkDataOnScreen()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
checkAndRequestPostNotificationsPermission()
}

processMindboxIntent(intent = intent, context = this)?.let { (url, payload) ->
binding.tvPushUrlResult.text = url
binding.tvPushPayloadResult.text = payload
proceedUrl(url = url)
}

binding.btnAsyncOperation.setOnClickListener {
//https://developers.mindbox.ru/docs/android-integration-of-actions
sendAsync(type = AsyncOperationType.OPERATION_BODY_JSON, context = this)
showToast(context = this, message = "Operation was sent")
}

binding.btnSyncOperation.setOnClickListener {
//https://developers.mindbox.ru/docs/android-integration-of-actions
sendSync(type = SyncOperationType.OPERATION_BODY_WITH_CUSTOM_RESPONSE, context = this)
showToast(context = this, message = "Sync operation was sent")
}
binding.btnOpenActivity.setOnClickListener {
val intent = Intent(this, ActivityTransitionByPush::class.java)
this.startActivity(intent)
}

binding.btnOpenPushList.setOnClickListener {
startActivity(Intent(this, NotificationHistoryActivity::class.java))
handlePushIntent(intent)

setContent {
MindboxTheme {
val darkTheme = isSystemInDarkTheme()
Box(Modifier.fillMaxSize()) {
MainScreen(
state = sdkInfo,
darkTheme = darkTheme,
onCopy = ::copyToClipboard,
onShowInApp = { showInAppSheet = true },
onSendAsync = {
// https://developers.mindbox.ru/docs/android-integration-of-actions
sendAsync(type = AsyncOperationType.OPERATION_BODY_JSON, context = this@MainActivity)
showToast(this@MainActivity, getString(R.string.toast_operation_sent))
},
onSendSync = {
// https://developers.mindbox.ru/docs/android-integration-of-actions
sendSync(type = SyncOperationType.OPERATION_BODY_WITH_CUSTOM_RESPONSE, context = this@MainActivity)
showToast(this@MainActivity, getString(R.string.toast_sync_operation_sent))
},
onOpenSecondActivity = {
startActivity(Intent(this@MainActivity, ActivityTransitionByPush::class.java))
},
onOpenHistory = {
startActivity(Intent(this@MainActivity, NotificationHistoryActivity::class.java))
},
showInAppSheet = showInAppSheet,
onDismissInAppSheet = { showInAppSheet = false },
onPickInApp = { option ->
showInAppSheet = false
// https://developers.mindbox.ru/docs/android-integration-of-actions
// Each option triggers its in-app via an async operation.
sendAsyncOperationWithEmptyBody(this@MainActivity, option.operationSystemName)
Log.d(
"InApp",
getString(R.string.toast_inapp_shown, getString(option.titleRes)),
)
},
)

if (showNavFragment) {
NavFragmentOverlay(darkTheme = darkTheme) { showNavFragment = false }
}
}
}
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
processMindboxIntent(intent = intent, context = this)?.let { (url, payload) ->
binding.tvPushUrlResult.text = url
binding.tvPushPayloadResult.text = payload
proceedUrl(url = url)
}
handlePushIntent(intent)
Mindbox.onNewIntent(intent)
}

override fun onDestroy() {
super.onDestroy()
_binding = null
private fun handlePushIntent(intent: Intent?) {
processMindboxIntent(intent = intent, context = this)?.let { (url, payload) ->
sdkInfo = sdkInfo.copy(pushUrl = url.orEmpty(), pushPayload = payload.orEmpty())
proceedUrl(url = url)
}
}

//https://developers.mindbox.ru/docs/android-sdk-methods#updatenotificationpermissionstatus-since-281
// https://developers.mindbox.ru/docs/android-sdk-methods#updatenotificationpermissionstatus-since-281
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
Expand All @@ -95,39 +135,68 @@ class MainActivity : AppCompatActivity() {
}
}

//navigation to fragments after click on push. Check url and open the required fragment
// navigation after click on push: check url and show the in-app fragment screen
private fun proceedUrl(url: String?) {
if (url == "https://gotofragment.com") {
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, FragmentForNavigation())
.addToBackStack(null)
.commit()
showNavFragment = true
}
}

private fun showSdkDataOnScreen() {
//https://developers.mindbox.ru/docs/android-sdk-methods#subscribedeviceuuid-%D0%B8-disposedeviceuuidsubscription
// https://developers.mindbox.ru/docs/android-sdk-methods#subscribedeviceuuid-%D0%B8-disposedeviceuuidsubscription
var subscriptionDeviceUuid = ""
subscriptionDeviceUuid = Mindbox.subscribeDeviceUuid { deviceUUID ->
runOnUiThread {
binding.tvDeviceUUIDResult.text = deviceUUID
}
runOnUiThread { sdkInfo = sdkInfo.copy(deviceUuid = deviceUUID) }
Mindbox.disposeDeviceUuidSubscription(subscriptionDeviceUuid)
}

//https://developers.mindbox.ru/docs/android-sdk-methods#subscribepushtoken-%D0%B8-disposepushtokensubscription
// https://developers.mindbox.ru/docs/android-sdk-methods#subscribepushtoken-%D0%B8-disposepushtokensubscription
var subscriptionPushToken = ""
subscriptionPushToken =
Mindbox.subscribePushTokens { tokens ->
runOnUiThread {
binding.tvTokenResult.text = tokens
//https://developers.mindbox.ru/docs/android-sdk-methods#getpushtokensavedate
binding.tvTokenDateResult.text = Mindbox.getPushTokensSaveDate().toString()
}
Mindbox.disposePushTokenSubscription(subscriptionPushToken)
subscriptionPushToken = Mindbox.subscribePushTokens { tokens ->
runOnUiThread {
sdkInfo = sdkInfo.copy(
token = tokens.orEmpty(),
// https://developers.mindbox.ru/docs/android-sdk-methods#getpushtokensavedate
tokenDate = Mindbox.getPushTokensSaveDate().toString(),
)
}
Mindbox.disposePushTokenSubscription(subscriptionPushToken)
}

// https://developers.mindbox.ru/docs/android-sdk-methods#getsdkversion
sdkInfo = sdkInfo.copy(sdkVersion = Mindbox.getSdkVersion())
}

private fun copyToClipboard(label: String, value: String) {
if (value.isEmpty()) return
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(label, value))
// Android 13+ shows its own copy confirmation bubble — avoid double toast
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
showToast(this, getString(R.string.toast_copied))
}
}
}

//https://developers.mindbox.ru/docs/android-sdk-methods#getsdkversion
binding.tvSdkVersionResult.text = Mindbox.getSdkVersion()
/** Replaces the old FragmentForNavigation — shown when a push deep-links to gotofragment.com. */
@androidx.compose.runtime.Composable
private fun NavFragmentOverlay(darkTheme: Boolean, onBack: () -> Unit) {
com.mindbox.example.ui.theme.SetStatusBarAppearance(darkIcons = !darkTheme)
androidx.activity.compose.BackHandler(onBack = onBack)
Column(
Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.systemBarsPadding()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center,
) {
Text(
androidx.compose.ui.res.stringResource(R.string.nav_fragment_message),
color = MaterialTheme.colorScheme.onSurface,
fontSize = 18.sp,
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MindboxFirebaseMessagingService : FirebaseMessagingService() {
super.onMessageReceived(message)
// Listing of links and activities that should be opened by different links
val activities = mapOf(
"https://newActivity.com" to ActivityTransitionByPush::class.java
SECOND_ACTIVITY_PUSH_URL to ActivityTransitionByPush::class.java
)
// Default Active. It will open if a link that is not in the list is received
val defaultActivity = MainActivity::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MindboxHuaweiMessagingService : HmsMessageService() {
val pushSmallIcon = R.mipmap.ic_launcher
// Listing of links and activites that should be opened by different links
val activities = mapOf(
"https://newActivity.com" to ActivityTransitionByPush::class.java
SECOND_ACTIVITY_PUSH_URL to ActivityTransitionByPush::class.java
)
// Default Active. It will open if a link that is not in the list is received
val defaultActivity = MainActivity::class.java
Expand Down
Loading