Skip to content
This repository has been archived by the owner on Oct 16, 2023. It is now read-only.

[BM-667] Feedback - baby device side #263

Open
wants to merge 1 commit into
base: BM-667_User_feedback
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ class SharedPreferencesModule {
fun provideConfigurationSharedPreferences(app: App): SharedPreferences =
app.getSharedPreferences(app.packageName + CONFIGURATION_PREFERENCES, Context.MODE_PRIVATE)

@FeedbackPreferencesQualifier
@Singleton
@Provides
fun provideFeedbackSharedPreferences(app: App): SharedPreferences =
app.getSharedPreferences(app.packageName + FEEDBACK_PREFERENCES, Context.MODE_PRIVATE)

companion object {
private const val CONFIGURATION_PREFERENCES = "configuration"
private const val FEEDBACK_PREFERENCES = "feedback"
}
}

@Qualifier
annotation class ConfigurationPreferencesQualifier
@Qualifier
annotation class FeedbackPreferencesQualifier
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.net.Uri
import co.netguru.baby.monitor.client.R
import co.netguru.baby.monitor.client.application.App
import co.netguru.baby.monitor.client.common.RunsInBackground
import co.netguru.baby.monitor.client.feature.voiceAnalysis.WavFileGenerator
import co.netguru.baby.monitor.client.feature.recording.RecordingFileController
import com.google.firebase.FirebaseApp
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageMetadata
Expand All @@ -24,7 +24,8 @@ class FirebaseRepository(
private val context: Context
) {
private var storageRef: StorageReference? = null
private val directory = context.getDir(WavFileGenerator.DIRECTORY_NAME, Context.MODE_PRIVATE)
private val directory = context.getDir(RecordingFileController.UPLOAD_RECORDINGS_DIRECTORY,
Context.MODE_PRIVATE)
internal val compositeDisposable = CompositeDisposable()

fun initializeApp(app: App) {
Expand Down Expand Up @@ -54,7 +55,7 @@ class FirebaseRepository(
}
}

internal fun isUploadEnablad() = preferencesWrapper.isUploadEnablad()
internal fun isUploadEnabled() = preferencesWrapper.isUploadEnabled()

internal fun setUploadEnabled(enable: Boolean) {
preferencesWrapper.setUploadEnabled(enable)
Expand All @@ -63,7 +64,7 @@ class FirebaseRepository(

@RunsInBackground
internal fun continueUploadingAfterProcessRestartIfNeeded() {
if (!preferencesWrapper.isUploadEnablad()) {
if (!preferencesWrapper.isUploadEnabled()) {
return
}
if (!preferencesWrapper.isFirebaseSessionResumable()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class FirebaseSharedPreferencesWrapper @Inject constructor(
getFileUriString() == null
}

fun isUploadEnablad() = preferences.getBoolean(
fun isUploadEnabled() = preferences.getBoolean(
FIREBASE_UPLOAD_ENABLED,
false
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.netguru.baby.monitor.client.common

import javax.inject.Inject

class TimestampProvider @Inject constructor() {
fun timestamp() = System.currentTimeMillis()
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class BabyMonitorMessagingService : FirebaseMessagingService() {
message.data
)
NotificationType.LOW_BATTERY_NOTIFICATION -> handleLowBatteryNotification(message.data)
NotificationType.CRY_NOTIFICATION_WITH_FEEDBACK_REQUEST,
NotificationType.NOISE_NOTIFICATION_WITH_FEEDBACK_REQUEST -> {
// TODO handle notification with feedback request

Choose a reason for hiding this comment

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

⚠️ This comment contains text that has been defined as forbidden in detekt.

handleBabyEvent(message.data)
}
else -> {
message.notification?.let {
handleRemoteNotification(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package co.netguru.baby.monitor.client.feature.babynotification

import co.netguru.baby.monitor.client.feature.feedback.SavedRecordingDetails
import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseNotificationSender
import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationData
import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationType
import io.reactivex.Completable
import io.reactivex.schedulers.Schedulers
Expand All @@ -15,14 +17,30 @@ class NotifyBabyEventUseCase(
private val babyEvents: PublishSubject<BabyEvent> = PublishSubject.create()

private fun fetchClientsAndPostNotification(babyEvent: BabyEvent): Completable {

Choose a reason for hiding this comment

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

⚠️ The function fetchClientsAndPostNotification appears to be too complex.

val (notificationTitle, notificationType) = when (babyEvent) {
BabyEvent.BabyCrying -> notificationTexts[CRY_TITLE_KEY] to NotificationType.CRY_NOTIFICATION
BabyEvent.NoiseDetected -> notificationTexts[NOISE_TITLE_KEY] to NotificationType.NOISE_NOTIFICATION
val notificationTitle = when (babyEvent) {
BabyEvent.BabyCrying, is BabyEvent.BabyCryingFeedback
-> notificationTexts[CRY_TITLE_KEY]
BabyEvent.NoiseDetected, is BabyEvent.NoiseDetectedFeedback
-> notificationTexts[NOISE_TITLE_KEY]
}
val notificationType = when (babyEvent) {
BabyEvent.BabyCrying -> NotificationType.CRY_NOTIFICATION
BabyEvent.NoiseDetected -> NotificationType.NOISE_NOTIFICATION
is BabyEvent.BabyCryingFeedback -> NotificationType.CRY_NOTIFICATION_WITH_FEEDBACK_REQUEST
is BabyEvent.NoiseDetectedFeedback -> NotificationType.NOISE_NOTIFICATION_WITH_FEEDBACK_REQUEST
}
val feedbackRecordingFile = when (babyEvent) {
is BabyEvent.BabyCryingFeedback -> babyEvent.recordingName
is BabyEvent.NoiseDetectedFeedback -> babyEvent.recordingName
else -> ""
}
return notificationSender.broadcastNotificationToFcm(
notificationTitle ?: error("Notification title missing"),
notificationTexts[NOTIFICATION_TEXT_KEY] ?: error("Notification text missing"),
notificationType
NotificationData(
notificationTitle ?: error("Notification title missing"),
notificationTexts[NOTIFICATION_TEXT_KEY] ?: error("Notification text missing"),
notificationType,
feedbackRecordingFile
)
)
}

Expand All @@ -37,15 +55,33 @@ class NotifyBabyEventUseCase(
.retry()
}

fun notifyBabyCrying() =
babyEvents.onNext(BabyEvent.BabyCrying)
fun notifyBabyCrying(savedRecordingDetails: SavedRecordingDetails? = null) {
val babyEvent = savedRecordingDetails?.let {
if (it.shouldAskForFeedback) {
BabyEvent.BabyCryingFeedback(it.fileName)
} else {
null
}
} ?: BabyEvent.BabyCrying
babyEvents.onNext(babyEvent)
}

fun notifyNoiseDetected() =
babyEvents.onNext(BabyEvent.NoiseDetected)
fun notifyNoiseDetected(savedRecordingDetails: SavedRecordingDetails? = null) {
val noiseEvent = savedRecordingDetails?.let {
if (it.shouldAskForFeedback) {
BabyEvent.NoiseDetectedFeedback(it.fileName)
} else {
null
}
} ?: BabyEvent.NoiseDetected
babyEvents.onNext(noiseEvent)
}

private sealed class BabyEvent {
object BabyCrying : BabyEvent()
object NoiseDetected : BabyEvent()
data class BabyCryingFeedback(val recordingName: String) : BabyEvent()
data class NoiseDetectedFeedback(val recordingName: String) : BabyEvent()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.netguru.baby.monitor.client.feature.batterylevel

import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseNotificationSender
import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationData
import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationType
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -10,9 +11,11 @@ class NotifyLowBatteryUseCase @Inject constructor(
) {
fun notifyLowBattery(title: String, text: String) =
notificationSender.broadcastNotificationToFcm(
title = title,
text = text,
notificationType = NotificationType.LOW_BATTERY_NOTIFICATION
notificationData = NotificationData(
title,
text,
NotificationType.LOW_BATTERY_NOTIFICATION
)
).also {
Timber.d("notifyLowBattery($title, $text)")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.netguru.baby.monitor.client.feature.feedback

import co.netguru.baby.monitor.client.application.firebase.FirebaseSharedPreferencesWrapper
import co.netguru.baby.monitor.client.feature.machinelearning.MachineLearning
import co.netguru.baby.monitor.client.feature.recording.RecordingFileController
import io.reactivex.Single
import javax.inject.Inject

class FeedbackController @Inject constructor(
private val feedbackFrequencyUseCase: FeedbackFrequencyUseCase,
private val firebaseSharedPreferencesWrapper: FirebaseSharedPreferencesWrapper,
private val recordingFileController: RecordingFileController
) {
fun handleRecording(
recordingData: ByteArray,
fromMachineLearning: Boolean
): Single<SavedRecordingDetails> {
return if (userEnabledRecordingUpload()) {
when {
feedbackFrequencyUseCase.shouldAskForFeedback() -> {
recordingFileController.saveRecording(recordingData)
.map { SavedRecordingDetails(it, true) }
}
fromMachineLearning -> {
recordingFileController.saveRecording(recordingData)
.map { SavedRecordingDetails(it) }
}
else -> {
Single.just(SavedRecordingDetails(RECORDING_NOT_SAVED))
}
}
} else {
Single.just(SavedRecordingDetails(RECORDING_NOT_SAVED))
}
}

private fun userEnabledRecordingUpload() = firebaseSharedPreferencesWrapper.isUploadEnabled()

companion object {
const val DATA_SIZE = MachineLearning.DATA_SIZE
const val RECORDING_NOT_SAVED = ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package co.netguru.baby.monitor.client.feature.feedback

import android.content.SharedPreferences
import co.netguru.baby.monitor.client.application.di.FeedbackPreferencesQualifier
import co.netguru.baby.monitor.client.common.TimestampProvider
import co.netguru.baby.monitor.client.common.extensions.edit
import java.util.concurrent.TimeUnit
import javax.inject.Inject

class FeedbackFrequencyUseCase @Inject constructor(
@FeedbackPreferencesQualifier
private val sharedPreferences: SharedPreferences,
private val timestampProvider: TimestampProvider
) {
fun shouldAskForFeedback(): Boolean {
val currentCounter = sharedPreferences.getInt(NOTIFICATION_COUNTER_KEY, 0)
val lastFeedbackTimestamp = sharedPreferences.getLong(FEEDBACK_TIMESTAMP_KEY, 0)
return enoughTimePassed(lastFeedbackTimestamp) && currentCounter > NO_FEEDBACK_COUNTER_LIMIT
}
Comment on lines +15 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it could be modeled with rx. Something like this, perhaps?

fun feedbackRecordings(recordings: Observable<Recording>) =
    Observable.interval(DELAY_HOURS, TimeUnit.HOURS)
        .withLatestFrom(recordings) { _, recording -> recording }


fun notificationSent() {
incrementCounter()
}

fun feedbackRequestSent() {
updateTimestamp()
clearNotificationCounter()
}

private fun enoughTimePassed(lastFeedbackTimestamp: Long) =
timestampProvider.timestamp() > lastFeedbackTimestamp + TimeUnit.HOURS.toMillis(
NO_FEEDBACK_REQUEST_HOURS
)

private fun updateTimestamp() {
sharedPreferences.edit {
putLong(FEEDBACK_TIMESTAMP_KEY, timestampProvider.timestamp())
}
}

private fun clearNotificationCounter() {
sharedPreferences.edit {
putInt(NOTIFICATION_COUNTER_KEY, CLEAR_COUNTER_VALUE)
}
}

private fun incrementCounter() {
val currentCounter = sharedPreferences.getInt(NOTIFICATION_COUNTER_KEY, 0)
sharedPreferences.edit {
putInt(NOTIFICATION_COUNTER_KEY, currentCounter + 1)
}
}

companion object {
internal const val FEEDBACK_TIMESTAMP_KEY = "feedback_timestamp"
internal const val NOTIFICATION_COUNTER_KEY = "notification_counter"
internal const val CLEAR_COUNTER_VALUE = 0
internal const val NO_FEEDBACK_REQUEST_HOURS = 3L
internal const val NO_FEEDBACK_COUNTER_LIMIT = 3
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.netguru.baby.monitor.client.feature.feedback

data class SavedRecordingDetails(
val fileName: String,
val shouldAskForFeedback: Boolean = false
)
Loading