Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: persistant in-app messages #269

Merged
merged 8 commits into from
Oct 25, 2023
Merged
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
1 change: 1 addition & 0 deletions messaginginapp/api/messaginginapp.api
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ public final class io/customer/messaginginapp/gist/presentation/GistSdk : androi
public static field dataCenter Ljava/lang/String;
public static field siteId Ljava/lang/String;
public final fun addListener (Lio/customer/messaginginapp/gist/presentation/GistListener;)V
public final fun clearCurrentMessage ()V
public final fun clearListeners ()V
public final fun clearUserToken ()V
public final fun dismissMessage ()V
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.customer.messaginginapp.gist.data.listeners

import android.content.Context
import android.util.Base64
import android.util.Log
import io.customer.messaginginapp.gist.data.NetworkUtilities
Expand All @@ -9,47 +10,94 @@
import io.customer.messaginginapp.gist.presentation.GIST_TAG
import io.customer.messaginginapp.gist.presentation.GistListener
import io.customer.messaginginapp.gist.presentation.GistSdk
import java.io.File
import java.util.regex.PatternSyntaxException
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class Queue : GistListener {

private var localMessageStore: MutableList<Message> = mutableListOf()
private val cacheMap = mutableMapOf<String, Response>()

init {
GistSdk.addListener(this)
}

private val cacheSize = 10 * 1024 * 1024 // 10 MB
private val cacheDirectory by lazy { File(GistSdk.application.cacheDir, "http_cache") }
private val cache by lazy { Cache(cacheDirectory, cacheSize.toLong()) }

private fun saveToPrefs(context: Context, key: String, value: String) {
val prefs = context.getSharedPreferences("network_cache", Context.MODE_PRIVATE)
prefs.edit().putString(key, value).apply()
}

Check warning on line 40 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt#L38-L40

Added lines #L38 - L40 were not covered by tests

private fun getFromPrefs(context: Context, key: String): String? {
val prefs = context.getSharedPreferences("network_cache", Context.MODE_PRIVATE)
return prefs.getString(key, null)

Check warning on line 44 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt#L43-L44

Added lines #L43 - L44 were not covered by tests
}

private val gistQueueService by lazy {
val httpClient: OkHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
GistSdk.getUserToken()?.let { userToken ->
val request: Request = chain.request().newBuilder()
.addHeader(NetworkUtilities.CIO_SITE_ID_HEADER, GistSdk.siteId)
.addHeader(NetworkUtilities.CIO_DATACENTER_HEADER, GistSdk.dataCenter)
.addHeader(
NetworkUtilities.USER_TOKEN_HEADER,
// The NO_WRAP flag will omit all line terminators (i.e., the output will be on one long line).
Base64.encodeToString(userToken.toByteArray(), Base64.NO_WRAP)
)
.build()
// Interceptor to set up request headers like site ID, data center, and user token.
val httpClient: OkHttpClient =
OkHttpClient.Builder().cache(cache)
.addInterceptor { chain ->

Check warning on line 51 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt#L49-L51

Added lines #L49 - L51 were not covered by tests
val originalRequest = chain.request()

chain.proceed(request)
} ?: run {
val request: Request = chain.request().newBuilder()
val networkRequest = originalRequest.newBuilder()
.addHeader(NetworkUtilities.CIO_SITE_ID_HEADER, GistSdk.siteId)
.addHeader(NetworkUtilities.CIO_DATACENTER_HEADER, GistSdk.dataCenter)
.apply {
GistSdk.getUserToken()?.let { userToken ->
addHeader(
NetworkUtilities.USER_TOKEN_HEADER,
// The NO_WRAP flag will omit all line terminators (i.e., the output will be on one long line).
Base64.encodeToString(userToken.toByteArray(), Base64.NO_WRAP)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the comment on it for future reference?

)
}
}
.header("Cache-Control", "no-cache")
.build()

chain.proceed(request)
val response = chain.proceed(networkRequest)

when (response.code) {
200 -> {
response.body?.let { responseBody ->
val responseBodyString = responseBody.string()
saveToPrefs(
GistSdk.application,
originalRequest.url.toString(),
responseBodyString
)
return@addInterceptor response.newBuilder().body(
responseBodyString.toResponseBody(responseBody.contentType())
).build()
}
}

304 -> {
val cachedResponse =
getFromPrefs(GistSdk.application, originalRequest.url.toString())
cachedResponse?.let {
return@addInterceptor response.newBuilder()
.body(it.toResponseBody(null)).code(200).build()
} ?: return@addInterceptor response
}

else -> return@addInterceptor response
}

response
}
}
.build()
.build()

Check warning on line 100 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt#L100

Added line #L100 was not covered by tests

Retrofit.Builder()
.baseUrl(GistSdk.gistEnvironment.getGistQueueApiUrl())
Expand All @@ -68,12 +116,10 @@
}

internal fun fetchUserMessages() {
GlobalScope.launch {

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / API check

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / Android Lint (messaginginapp)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / Android Lint (messaginginapp)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / Unit tests (messaginginapp)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / API check

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / instrumentation-test (java_layout)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / instrumentation-test (kotlin_compose)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / instrumentation-test (java_layout)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / Unit tests (messaginginapp)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.

Check warning on line 119 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/listeners/Queue.kt

View workflow job for this annotation

GitHub Actions / instrumentation-test (kotlin_compose)

This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.
try {
Log.i(GIST_TAG, "Fetching user messages")
val latestMessagesResponse = gistQueueService.fetchMessagesForUser()
// If there's no change (304), move on.
if (latestMessagesResponse.code() == 304) { return@launch }

// To prevent us from showing expired / revoked messages, clear user messages from local queue.
clearUserMessagesFromLocalStore()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class GistMessageProperties {
}
}
gistProperties["persistent"]?.let { id ->
(id as Boolean).let { persistentValue ->
(id as? Boolean)?.let { persistentValue ->
persistent = persistentValue
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.doOnEnd
import com.google.gson.Gson
Expand Down Expand Up @@ -61,6 +62,12 @@
} ?: run {
finish()
}

// Update back button to handle in-app message behavior, disable back press for persistent messages, true otherwise
val onBackPressedCallback = object : OnBackPressedCallback(isPersistentMessage()) {
override fun handleOnBackPressed() {}

Check warning on line 68 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt#L67-L68

Added lines #L67 - L68 were not covered by tests
}
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

Check warning on line 70 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt#L70

Added line #L70 was not covered by tests
}

override fun onResume() {
Expand Down Expand Up @@ -91,10 +98,21 @@

override fun onDestroy() {
GistSdk.removeListener(this)
GistSdk.dismissMessage()
// If the message is not persistent, dismiss it and inform the callback
if (!isPersistentMessage()) {
GistSdk.dismissMessage()

Check warning on line 103 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt#L103

Added line #L103 was not covered by tests
} else {
GistSdk.clearCurrentMessage()

Check warning on line 105 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt#L105

Added line #L105 was not covered by tests
}
super.onDestroy()
}

private fun isPersistentMessage(): Boolean = currentMessage?.let {
GistMessageProperties.getGistProperties(
it
).persistent
} ?: false

Check warning on line 114 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt#L111-L114

Added lines #L111 - L114 were not covered by tests

override fun onMessageShown(message: Message) {
runOnUiThread {
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

internal fun showModalMessage(message: Message, position: MessagePosition? = null): Boolean {
currentMessage?.let { currentMessage ->
Log.i(GIST_TAG, "Message ${message.messageId} not shown, ${currentMessage.messageId} is already showing.")
Log.i(
GIST_TAG,
"Message ${message.messageId} not shown, ${currentMessage.messageId} is already showing."

Check warning on line 20 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalManager.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalManager.kt#L18-L20

Added lines #L18 - L20 were not covered by tests
)
return false
}

Expand Down Expand Up @@ -55,4 +58,8 @@
override fun onMessageShown(message: Message) {}

override fun onAction(message: Message, currentRoute: String, action: String, name: String) {}

internal fun clearCurrentMessage() {
currentMessage = null
}

Check warning on line 64 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalManager.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalManager.kt#L63-L64

Added lines #L63 - L64 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@
gistModalManager.dismissActiveMessage()
}

fun clearCurrentMessage() {
gistModalManager.clearCurrentMessage()
}

Check warning on line 165 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt

View check run for this annotation

Codecov / codecov/patch

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt#L164-L165

Added lines #L164 - L165 were not covered by tests

// Listeners

fun addListener(listener: GistListener) {
Expand Down
Loading