From 8f8c0fad05bcfa33868a669f514c9cf1484b15c7 Mon Sep 17 00:00:00 2001 From: Ankur Date: Sun, 21 Jan 2024 00:05:17 +0530 Subject: [PATCH] Provide AnalyticsHelper using Hilt and inject it directly into dependent components. --- .../java/com/waseefakhtar/doseapp/DoseApp.kt | 10 +- .../com/waseefakhtar/doseapp/MainActivity.kt | 9 +- .../doseapp/MedicationNotificationReceiver.kt | 9 +- .../doseapp/MedicationNotificationService.kt | 4 +- .../doseapp/analytics/AnalyticsHelper.kt | 45 +++--- .../doseapp/di/AnalyticsHelperModule.kt | 23 +++ .../addmedication/AddMedicationRoute.kt | 23 ++- .../viewmodel/AddMedicationViewModel.kt | 12 +- .../doseapp/feature/history/HistoryScreen.kt | 23 ++- .../doseapp/feature/home/HomeScreen.kt | 134 +++++++++++++----- .../feature/home/viewmodel/HomeViewModel.kt | 8 +- .../MedicationConfirmRoute.kt | 18 ++- .../viewmodel/MedicationConfirmViewModel.kt | 13 +- .../medicationdetail/MedicationDetailRoute.kt | 11 +- .../viewmodel/MedicationDetailViewModel.kt | 8 +- 15 files changed, 241 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/com/waseefakhtar/doseapp/di/AnalyticsHelperModule.kt diff --git a/app/src/main/java/com/waseefakhtar/doseapp/DoseApp.kt b/app/src/main/java/com/waseefakhtar/doseapp/DoseApp.kt index 18c0a5e..0b29a6a 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/DoseApp.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/DoseApp.kt @@ -54,7 +54,9 @@ import com.waseefakhtar.doseapp.ui.theme.DoseAppTheme import com.waseefakhtar.doseapp.util.SnackbarUtil @Composable -fun DoseApp() { +fun DoseApp( + analyticsHelper: AnalyticsHelper +) { DoseAppTheme { // A surface container using the 'background' color from the theme Surface( @@ -73,9 +75,6 @@ fun DoseApp() { val bottomBarVisibility = rememberSaveable { (mutableStateOf(true)) } val fabVisibility = rememberSaveable { (mutableStateOf(true)) } - val context = LocalContext.current - val analyticsHelper = AnalyticsHelper.getInstance(context) - Scaffold( modifier = Modifier.padding(16.dp, 0.dp), containerColor = Color.Transparent, @@ -216,7 +215,8 @@ fun DoseFAB(navController: NavController, analyticsHelper: AnalyticsHelper) { @Preview(showBackground = true) @Composable fun DefaultPreview() { + val context = LocalContext.current DoseAppTheme { - DoseApp() + DoseApp(analyticsHelper = AnalyticsHelper(context = context)) } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/MainActivity.kt b/app/src/main/java/com/waseefakhtar/doseapp/MainActivity.kt index c857636..aaf60ad 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/MainActivity.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/MainActivity.kt @@ -7,13 +7,18 @@ import androidx.activity.compose.setContent import com.waseefakhtar.doseapp.analytics.AnalyticsEvents import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + + @Inject + lateinit var analyticsHelper: AnalyticsHelper + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - DoseApp() + DoseApp(analyticsHelper = analyticsHelper) } parseIntent(intent) } @@ -21,7 +26,7 @@ class MainActivity : ComponentActivity() { private fun parseIntent(intent: Intent?) { val isMedicationNotification = intent?.getBooleanExtra(MEDICATION_NOTIFICATION, false) ?: false if (isMedicationNotification) { - AnalyticsHelper.getInstance(this).logEvent(AnalyticsEvents.REMINDER_NOTIFICATION_CLICKED) + analyticsHelper.logEvent(AnalyticsEvents.REMINDER_NOTIFICATION_CLICKED) } } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationReceiver.kt b/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationReceiver.kt index 3f5c25d..ae336aa 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationReceiver.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationReceiver.kt @@ -9,11 +9,18 @@ import android.os.Build import androidx.core.app.NotificationCompat import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject const val MEDICATION_INTENT = "medication_intent" const val MEDICATION_NOTIFICATION = "medication_notification" + +@AndroidEntryPoint class MedicationNotificationReceiver : BroadcastReceiver() { + @Inject + lateinit var analyticsHelper: AnalyticsHelper + override fun onReceive(context: Context?, intent: Intent?) { context?.let { intent?.getParcelableExtra(MEDICATION_INTENT)?.let { medication -> @@ -59,6 +66,6 @@ class MedicationNotificationReceiver : BroadcastReceiver() { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(medication.hashCode(), notification) - AnalyticsHelper.getInstance(context).trackNotificationShown(medication) + analyticsHelper.trackNotificationShown(medication) } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationService.kt b/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationService.kt index 515a64c..e730ed9 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationService.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/MedicationNotificationService.kt @@ -13,7 +13,7 @@ class MedicationNotificationService( private val context: Context ) { - fun scheduleNotification(medication: Medication) { + fun scheduleNotification(medication: Medication, analyticsHelper: AnalyticsHelper) { val intent = Intent(context, MedicationNotificationReceiver::class.java) intent.putExtra(MEDICATION_INTENT, medication) @@ -40,7 +40,7 @@ class MedicationNotificationService( } } - AnalyticsHelper.getInstance(context).trackNotificationScheduled(medication) + analyticsHelper.trackNotificationScheduled(medication) } companion object { diff --git a/app/src/main/java/com/waseefakhtar/doseapp/analytics/AnalyticsHelper.kt b/app/src/main/java/com/waseefakhtar/doseapp/analytics/AnalyticsHelper.kt index 0f3fe14..502135f 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/analytics/AnalyticsHelper.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/analytics/AnalyticsHelper.kt @@ -2,43 +2,40 @@ package com.waseefakhtar.doseapp.analytics import android.content.Context import android.os.Bundle +import androidx.core.os.bundleOf import com.google.firebase.analytics.FirebaseAnalytics import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.toFormattedDateString import java.util.Date -class AnalyticsHelper private constructor(context: Context) { +private const val MEDICATION_TIME = "medication_time" +private const val MEDICATION_END_DATE = "medication_end_date" +private const val NOTIFICATION_TIME = "notification_time" +class AnalyticsHelper( + context: Context +) { private val firebaseAnalytics = FirebaseAnalytics.getInstance(context) - companion object { - @Volatile - private var instance: AnalyticsHelper? = null - - fun getInstance(context: Context): AnalyticsHelper { - return instance ?: synchronized(this) { - instance ?: AnalyticsHelper(context).also { instance = it } - } - } - } - - fun logEvent(eventName: String, params: Bundle? = null) { - firebaseAnalytics.logEvent(eventName, params) - } - fun trackNotificationShown(medication: Medication) { - val params = Bundle() - params.putString("medication_time", medication.medicationTime.toFormattedDateString()) - params.putString("medication_end_date", medication.endDate.toFormattedDateString()) - params.putString("notification_time", Date().toFormattedDateString()) + val params = bundleOf( + MEDICATION_TIME to medication.medicationTime.toFormattedDateString(), + MEDICATION_END_DATE to medication.endDate.toFormattedDateString(), + NOTIFICATION_TIME to Date().toFormattedDateString() + ) logEvent(AnalyticsEvents.MEDICATION_NOTIFICATION_SHOWN, params) } fun trackNotificationScheduled(medication: Medication) { - val params = Bundle() - params.putString("medication_time", medication.medicationTime.toFormattedDateString()) - params.putString("medication_end_date", medication.endDate.toFormattedDateString()) - params.putString("notification_time", Date().toFormattedDateString()) + val params = bundleOf( + MEDICATION_TIME to medication.medicationTime.toFormattedDateString(), + MEDICATION_END_DATE to medication.endDate.toFormattedDateString(), + NOTIFICATION_TIME to Date().toFormattedDateString() + ) logEvent(AnalyticsEvents.MEDICATION_NOTIFICATION_SCHEDULED, params) } + + fun logEvent(eventName: String, params: Bundle? = null) { + firebaseAnalytics.logEvent(eventName, params) + } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/di/AnalyticsHelperModule.kt b/app/src/main/java/com/waseefakhtar/doseapp/di/AnalyticsHelperModule.kt new file mode 100644 index 0000000..0c5b383 --- /dev/null +++ b/app/src/main/java/com/waseefakhtar/doseapp/di/AnalyticsHelperModule.kt @@ -0,0 +1,23 @@ +package com.waseefakhtar.doseapp.di + +import android.content.Context +import com.waseefakhtar.doseapp.analytics.AnalyticsHelper +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AnalyticsHelperModule { + + @Provides + @Singleton + fun provideAnalyticsHelper( + @ApplicationContext context: Context, + ): AnalyticsHelper { + return AnalyticsHelper(context = context) + } +} diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/AddMedicationRoute.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/AddMedicationRoute.kt index b852b05..03177ec 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/AddMedicationRoute.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/AddMedicationRoute.kt @@ -56,7 +56,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.waseefakhtar.doseapp.R import com.waseefakhtar.doseapp.analytics.AnalyticsEvents -import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.toFormattedDateString import com.waseefakhtar.doseapp.feature.addmedication.model.CalendarInformation @@ -80,8 +79,7 @@ fun AddMedicationRoute( navigateToMedicationConfirm: (List) -> Unit, viewModel: AddMedicationViewModel = hiltViewModel() ) { - val analyticsHelper = AnalyticsHelper.getInstance(LocalContext.current) - AddMedicationScreen(onBackClicked, viewModel, analyticsHelper, navigateToMedicationConfirm) + AddMedicationScreen(onBackClicked, viewModel, navigateToMedicationConfirm) } @OptIn(ExperimentalMaterial3Api::class) @@ -89,7 +87,6 @@ fun AddMedicationRoute( fun AddMedicationScreen( onBackClicked: () -> Unit, viewModel: AddMedicationViewModel, - analyticsHelper: AnalyticsHelper, navigateToMedicationConfirm: (List) -> Unit, ) { var medicationName by rememberSaveable { mutableStateOf("") } @@ -101,12 +98,12 @@ fun AddMedicationScreen( fun addTime(time: CalendarInformation) { selectedTimes.add(time) - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_ADD_TIME_CLICKED) + viewModel.logEvent(eventName = AnalyticsEvents.ADD_MEDICATION_ADD_TIME_CLICKED) } fun removeTime(time: CalendarInformation) { selectedTimes.remove(time) - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_DELETE_TIME_CLICKED) + viewModel.logEvent(eventName = AnalyticsEvents.ADD_MEDICATION_DELETE_TIME_CLICKED) } Scaffold( @@ -117,7 +114,7 @@ fun AddMedicationScreen( navigationIcon = { FloatingActionButton( onClick = { - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_ON_BACK_CLICKED) + viewModel.logEvent(eventName = AnalyticsEvents.ADD_MEDICATION_ON_BACK_CLICKED) onBackClicked() }, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp) @@ -164,11 +161,11 @@ fun AddMedicationScreen( AnalyticsEvents.ADD_MEDICATION_MEDICATION_VALUE_INVALIDATED, invalidatedValue ) - analyticsHelper.logEvent(event) + viewModel.logEvent(eventName = event) }, onValidate = { navigateToMedicationConfirm(it) - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_NAVIGATING_TO_MEDICATION_CONFIRM) + viewModel.logEvent(eventName = AnalyticsEvents.ADD_MEDICATION_NAVIGATING_TO_MEDICATION_CONFIRM) }, viewModel = viewModel ) @@ -277,7 +274,9 @@ fun AddMedicationScreen( selectedTimes[index] = it }, onDeleteClick = { removeTime(selectedTimes[index]) }, - analyticsHelper = analyticsHelper + logEvent = { + viewModel.logEvent(AnalyticsEvents.ADD_MEDICATION_NEW_TIME_SELECTED) + }, ) } @@ -471,7 +470,7 @@ fun TimerTextField( isOnlyItem: Boolean, time: (CalendarInformation) -> Unit, onDeleteClick: () -> Unit, - analyticsHelper: AnalyticsHelper + logEvent: () -> Unit ) { val interactionSource = remember { MutableInteractionSource() } val isPressed: Boolean by interactionSource.collectIsPressedAsState() @@ -484,7 +483,7 @@ fun TimerTextField( showDialog = isPressed, selectedDate = selectedTime, onSelectedTime = { - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_NEW_TIME_SELECTED) + logEvent.invoke() selectedTime = it time(it) } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/viewmodel/AddMedicationViewModel.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/viewmodel/AddMedicationViewModel.kt index 58dd8ef..b03bf2a 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/viewmodel/AddMedicationViewModel.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/addmedication/viewmodel/AddMedicationViewModel.kt @@ -1,12 +1,18 @@ package com.waseefakhtar.doseapp.feature.addmedication.viewmodel import androidx.lifecycle.ViewModel +import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.feature.addmedication.model.CalendarInformation +import dagger.hilt.android.lifecycle.HiltViewModel import java.util.Calendar import java.util.Date +import javax.inject.Inject -class AddMedicationViewModel : ViewModel() { +@HiltViewModel +class AddMedicationViewModel @Inject constructor( + private val analyticsHelper: AnalyticsHelper +) : ViewModel() { fun createMedications( name: String, @@ -59,4 +65,8 @@ class AddMedicationViewModel : ViewModel() { calendar.set(Calendar.MINUTE, medicationTime.dateInformation.minute) return calendar.time } + + fun logEvent(eventName: String) { + analyticsHelper.logEvent(eventName = eventName) + } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/history/HistoryScreen.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/history/HistoryScreen.kt index f96328e..d20e3ec 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/history/HistoryScreen.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/history/HistoryScreen.kt @@ -16,14 +16,12 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.waseefakhtar.doseapp.R -import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.hasPassed import com.waseefakhtar.doseapp.feature.history.viewmodel.HistoryState @@ -36,14 +34,19 @@ fun HistoryRoute( navigateToMedicationDetail: (Medication) -> Unit, viewModel: HistoryViewModel = hiltViewModel() ) { - val analyticsHelper = AnalyticsHelper.getInstance(LocalContext.current) val state = viewModel.state - HistoryScreen(analyticsHelper, state, navigateToMedicationDetail) + HistoryScreen( + state = state, + navigateToMedicationDetail = navigateToMedicationDetail + ) } @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HistoryScreen(analyticsHelper: AnalyticsHelper, state: HistoryState, navigateToMedicationDetail: (Medication) -> Unit) { +fun HistoryScreen( + state: HistoryState, + navigateToMedicationDetail: (Medication) -> Unit +) { Scaffold( topBar = { TopAppBar( @@ -64,13 +67,19 @@ fun HistoryScreen(analyticsHelper: AnalyticsHelper, state: HistoryState, navigat modifier = Modifier.padding(innerPadding), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - MedicationList(analyticsHelper, state, navigateToMedicationDetail) + MedicationList( + state = state, + navigateToMedicationDetail = navigateToMedicationDetail + ) } } } @Composable -fun MedicationList(analyticsHelper: AnalyticsHelper, state: HistoryState, navigateToMedicationDetail: (Medication) -> Unit) { +fun MedicationList( + state: HistoryState, + navigateToMedicationDetail: (Medication) -> Unit +) { val filteredMedicationList = state.medications.filter { it.medicationTime.hasPassed() } val sortedMedicationList: List = filteredMedicationList.sortedBy { it.medicationTime }.map { MedicationListItem.MedicationItem(it) } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/home/HomeScreen.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/home/HomeScreen.kt index 243c1be..ed885d5 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/home/HomeScreen.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/home/HomeScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -52,7 +53,6 @@ import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.waseefakhtar.doseapp.R import com.waseefakhtar.doseapp.analytics.AnalyticsEvents -import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.toFormattedDateShortString import com.waseefakhtar.doseapp.extension.toFormattedDateString @@ -74,19 +74,44 @@ fun HomeRoute( modifier: Modifier = Modifier, viewModel: HomeViewModel = hiltViewModel() ) { - val analyticsHelper = AnalyticsHelper.getInstance(LocalContext.current) val state = viewModel.state - PermissionAlarmDialog(analyticsHelper, askAlarmPermission) - PermissionDialog(analyticsHelper, askNotificationPermission) - HomeScreen(navController, analyticsHelper, state, viewModel, navigateToMedicationDetail) + PermissionAlarmDialog( + askAlarmPermission = askAlarmPermission, + logEvent = viewModel::logEvent + ) + PermissionDialog( + askNotificationPermission = askNotificationPermission, + logEvent = viewModel::logEvent + ) + HomeScreen( + modifier = modifier, + navController = navController, + state = state, + navigateToMedicationDetail = navigateToMedicationDetail, + logEvent = viewModel::logEvent + ) } @Composable -fun HomeScreen(navController: NavController, analyticsHelper: AnalyticsHelper, state: HomeState, viewModel: HomeViewModel, navigateToMedicationDetail: (Medication) -> Unit) { +fun HomeScreen( + modifier: Modifier, + navController: NavController, + state: HomeState, + navigateToMedicationDetail: (Medication) -> Unit, + logEvent: (String) -> Unit +) { Column( + modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp) ) { - DailyMedications(navController, analyticsHelper, state, viewModel, navigateToMedicationDetail) + DailyMedications( + navController = navController, + state = state, + navigateToMedicationDetail = navigateToMedicationDetail, + logEvent = { + logEvent.invoke(it) + } + ) } } @@ -110,7 +135,11 @@ fun Greeting() { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun DailyOverviewCard(navController: NavController, analyticsHelper: AnalyticsHelper, medicationsToday: List) { +fun DailyOverviewCard( + navController: NavController, + medicationsToday: List, + logEvent: (String) -> Unit +) { Card( modifier = Modifier @@ -123,7 +152,7 @@ fun DailyOverviewCard(navController: NavController, analyticsHelper: AnalyticsHe contentColor = MaterialTheme.colorScheme.tertiary ), onClick = { - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_CLICKED_DAILY_OVERVIEW) + logEvent.invoke(AnalyticsEvents.ADD_MEDICATION_CLICKED_DAILY_OVERVIEW) navController.navigate(AddMedicationDestination.route) } ) { @@ -167,8 +196,15 @@ fun DailyOverviewCard(navController: NavController, analyticsHelper: AnalyticsHe @OptIn(ExperimentalMaterial3Api::class) @Composable -fun EmptyCard(navController: NavController, analyticsHelper: AnalyticsHelper) { - analyticsHelper.logEvent(AnalyticsEvents.EMPTY_CARD_SHOWN) +fun EmptyCard( + navController: NavController, + logEvent: (String) -> Unit +) { + + LaunchedEffect(Unit) { + logEvent.invoke(AnalyticsEvents.EMPTY_CARD_SHOWN) + } + Card( modifier = Modifier .fillMaxWidth() @@ -179,7 +215,7 @@ fun EmptyCard(navController: NavController, analyticsHelper: AnalyticsHelper) { contentColor = MaterialTheme.colorScheme.tertiary ), onClick = { - analyticsHelper.logEvent(AnalyticsEvents.ADD_MEDICATION_CLICKED_EMPTY_CARD) + logEvent.invoke(AnalyticsEvents.ADD_MEDICATION_CLICKED_EMPTY_CARD) navController.navigate(AddMedicationDestination.route) } ) { @@ -218,23 +254,38 @@ fun EmptyCard(navController: NavController, analyticsHelper: AnalyticsHelper) { } @Composable -fun DailyMedications(navController: NavController, analyticsHelper: AnalyticsHelper, state: HomeState, viewModel: HomeViewModel, navigateToMedicationDetail: (Medication) -> Unit) { +fun DailyMedications( + navController: NavController, + state: HomeState, + navigateToMedicationDetail: (Medication) -> Unit, + logEvent: (String) -> Unit +) { var filteredMedications: List by remember { mutableStateOf(emptyList()) } - DatesHeader(analyticsHelper) { selectedDate -> - val newMedicationList = state.medications - .filter { medication -> - medication.medicationTime.toFormattedDateString() == selectedDate.date.toFormattedDateString() - } - .sortedBy { it.medicationTime } + DatesHeader( + logEvent = { + logEvent.invoke(it) + }, + onDateSelected = { selectedDate -> + val newMedicationList = state.medications + .filter { medication -> + medication.medicationTime.toFormattedDateString() == selectedDate.date.toFormattedDateString() + } + .sortedBy { it.medicationTime } - filteredMedications = newMedicationList - analyticsHelper.logEvent(AnalyticsEvents.HOME_NEW_DATE_SELECTED) - } + filteredMedications = newMedicationList + logEvent.invoke(AnalyticsEvents.HOME_NEW_DATE_SELECTED) + } + ) if (filteredMedications.isEmpty()) { - EmptyCard(navController, analyticsHelper) + EmptyCard( + navController = navController, + logEvent = { + logEvent.invoke(it) + } + ) } else { LazyColumn( modifier = Modifier, @@ -256,7 +307,7 @@ fun DailyMedications(navController: NavController, analyticsHelper: AnalyticsHel @Composable fun DatesHeader( - analyticsHelper: AnalyticsHelper, + logEvent: (String) -> Unit, onDateSelected: (CalendarModel.DateModel) -> Unit // Callback to pass the selected date ) { val dataSource = CalendarDataSource() @@ -278,7 +329,7 @@ fun DatesHeader( val finalStartDate = calendar.time calendarModel = dataSource.getData(startDate = finalStartDate, lastSelectedDate = calendarModel.selectedDate.date) - analyticsHelper.logEvent(AnalyticsEvents.HOME_CALENDAR_PREVIOUS_WEEK_CLICKED) + logEvent.invoke(AnalyticsEvents.HOME_CALENDAR_PREVIOUS_WEEK_CLICKED) }, onNextClickListener = { endDate -> // refresh the CalendarModel with new data @@ -290,7 +341,7 @@ fun DatesHeader( val finalStartDate = calendar.time calendarModel = dataSource.getData(startDate = finalStartDate, lastSelectedDate = calendarModel.selectedDate.date) - analyticsHelper.logEvent(AnalyticsEvents.HOME_CALENDAR_NEXT_WEEK_CLICKED) + logEvent.invoke(AnalyticsEvents.HOME_CALENDAR_NEXT_WEEK_CLICKED) } ) DateList( @@ -427,12 +478,15 @@ sealed class MedicationListItem { @OptIn(ExperimentalPermissionsApi::class) @Composable -fun PermissionDialog(analyticsHelper: AnalyticsHelper, askNotificationPermission: Boolean) { +fun PermissionDialog( + askNotificationPermission: Boolean, + logEvent: (String) -> Unit +) { if (askNotificationPermission && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)) { val notificationPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS) { isGranted -> when (isGranted) { - true -> analyticsHelper.logEvent(AnalyticsEvents.NOTIFICATION_PERMISSION_GRANTED) - false -> analyticsHelper.logEvent(AnalyticsEvents.NOTIFICATION_PERMISSION_REFUSED) + true -> logEvent.invoke(AnalyticsEvents.NOTIFICATION_PERMISSION_GRANTED) + false -> logEvent.invoke(AnalyticsEvents.NOTIFICATION_PERMISSION_REFUSED) } } if (!notificationPermissionState.status.isGranted) { @@ -440,7 +494,7 @@ fun PermissionDialog(analyticsHelper: AnalyticsHelper, askNotificationPermission when { openAlertDialog.value -> { - analyticsHelper.logEvent(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_SHOWN) + logEvent.invoke(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_SHOWN) AlertDialog( icon = { Icon( @@ -456,14 +510,14 @@ fun PermissionDialog(analyticsHelper: AnalyticsHelper, askNotificationPermission }, onDismissRequest = { openAlertDialog.value = false - analyticsHelper.logEvent(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_DISMISSED) + logEvent.invoke(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_DISMISSED) }, confirmButton = { Button( onClick = { notificationPermissionState.launchPermissionRequest() openAlertDialog.value = false - analyticsHelper.logEvent(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_ALLOW_CLICKED) + logEvent.invoke(AnalyticsEvents.NOTIFICATION_PERMISSION_DIALOG_ALLOW_CLICKED) } ) { Text(stringResource(R.string.allow)) @@ -478,14 +532,17 @@ fun PermissionDialog(analyticsHelper: AnalyticsHelper, askNotificationPermission @OptIn(ExperimentalPermissionsApi::class) @Composable -fun PermissionAlarmDialog(analyticsHelper: AnalyticsHelper, askAlarmPermission: Boolean) { +fun PermissionAlarmDialog( + askAlarmPermission: Boolean, + logEvent: (String) -> Unit +) { val context = LocalContext.current val alarmManager = ContextCompat.getSystemService(context, AlarmManager::class.java) if (askAlarmPermission && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) { val alarmPermissionState = rememberPermissionState(Manifest.permission.SCHEDULE_EXACT_ALARM) { isGranted -> when (isGranted) { - true -> analyticsHelper.logEvent(AnalyticsEvents.ALARM_PERMISSION_GRANTED) - false -> analyticsHelper.logEvent(AnalyticsEvents.ALARM_PERMISSION_REFUSED) + true -> logEvent.invoke(AnalyticsEvents.ALARM_PERMISSION_GRANTED) + false -> logEvent.invoke(AnalyticsEvents.ALARM_PERMISSION_REFUSED) } } if (alarmManager?.canScheduleExactAlarms() == false) { @@ -493,7 +550,8 @@ fun PermissionAlarmDialog(analyticsHelper: AnalyticsHelper, askAlarmPermission: when { openAlertDialog.value -> { - analyticsHelper.logEvent(AnalyticsEvents.ALARM_PERMISSION_DIALOG_SHOWN) + + logEvent.invoke(AnalyticsEvents.ALARM_PERMISSION_DIALOG_SHOWN) AlertDialog( icon = { @@ -510,7 +568,7 @@ fun PermissionAlarmDialog(analyticsHelper: AnalyticsHelper, askAlarmPermission: }, onDismissRequest = { openAlertDialog.value = false - analyticsHelper.logEvent(AnalyticsEvents.ALARM_PERMISSION_DIALOG_DISMISSED) + logEvent.invoke(AnalyticsEvents.ALARM_PERMISSION_DIALOG_DISMISSED) }, confirmButton = { Button( @@ -521,7 +579,7 @@ fun PermissionAlarmDialog(analyticsHelper: AnalyticsHelper, askAlarmPermission: } openAlertDialog.value = false - analyticsHelper.logEvent(AnalyticsEvents.ALARM_PERMISSION_DIALOG_ALLOW_CLICKED) + logEvent.invoke(AnalyticsEvents.ALARM_PERMISSION_DIALOG_ALLOW_CLICKED) } ) { Text(stringResource(R.string.allow)) diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/home/viewmodel/HomeViewModel.kt index 1844596..1ffdaf9 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/home/viewmodel/HomeViewModel.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.feature.home.usecase.GetMedicationsUseCase import com.waseefakhtar.doseapp.feature.home.usecase.UpdateMedicationUseCase @@ -17,7 +18,8 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val getMedicationsUseCase: GetMedicationsUseCase, - private val updateMedicationUseCase: UpdateMedicationUseCase + private val updateMedicationUseCase: UpdateMedicationUseCase, + private val analyticsHelper: AnalyticsHelper ) : ViewModel() { var state by mutableStateOf(HomeState()) @@ -56,4 +58,8 @@ class HomeViewModel @Inject constructor( fun getUserPlan() { // TODO: Get user plan } + + fun logEvent(eventName: String) { + analyticsHelper.logEvent(eventName = eventName) + } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/MedicationConfirmRoute.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/MedicationConfirmRoute.kt index 568170d..604f8c6 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/MedicationConfirmRoute.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/MedicationConfirmRoute.kt @@ -27,7 +27,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.firebase.crashlytics.FirebaseCrashlytics import com.waseefakhtar.doseapp.R import com.waseefakhtar.doseapp.analytics.AnalyticsEvents -import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.toFormattedDateString import com.waseefakhtar.doseapp.feature.medicationconfirm.viewmodel.MedicationConfirmState @@ -42,9 +41,14 @@ fun MedicationConfirmRoute( modifier: Modifier = Modifier, viewModel: MedicationConfirmViewModel = hiltViewModel() ) { - val analyticsHelper = AnalyticsHelper.getInstance(LocalContext.current) medication?.let { - MedicationConfirmScreen(it, viewModel, analyticsHelper, onBackClicked, navigateToHome) + MedicationConfirmScreen( + medications = it, + viewModel = viewModel, + onBackClicked = onBackClicked, + navigateToHome = navigateToHome, + logEvent = viewModel::logEvent + ) } ?: { FirebaseCrashlytics.getInstance().log("Error: Cannot show MedicationConfirmScreen. Medication is null.") } @@ -54,7 +58,7 @@ fun MedicationConfirmRoute( fun MedicationConfirmScreen( medications: List, viewModel: MedicationConfirmViewModel, - analyticsHelper: AnalyticsHelper, + logEvent: (String) -> Unit, onBackClicked: () -> Unit, navigateToHome: () -> Unit, ) { @@ -71,7 +75,7 @@ fun MedicationConfirmScreen( ) ) navigateToHome() - analyticsHelper.logEvent(AnalyticsEvents.MEDICATIONS_SAVED) + logEvent.invoke(AnalyticsEvents.MEDICATIONS_SAVED) } } @@ -81,7 +85,7 @@ fun MedicationConfirmScreen( ) { FloatingActionButton( onClick = { - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_CONFIRM_ON_BACK_CLICKED) + logEvent.invoke(AnalyticsEvents.MEDICATION_CONFIRM_ON_BACK_CLICKED) onBackClicked() }, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp) @@ -130,7 +134,7 @@ fun MedicationConfirmScreen( .height(56.dp) .align(Alignment.CenterHorizontally), onClick = { - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_CONFIRM_ON_CONFIRM_CLICKED) + logEvent.invoke(AnalyticsEvents.MEDICATION_CONFIRM_ON_CONFIRM_CLICKED) viewModel.addMedication(context, MedicationConfirmState(medications)) }, shape = MaterialTheme.shapes.extraLarge diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/viewmodel/MedicationConfirmViewModel.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/viewmodel/MedicationConfirmViewModel.kt index 7883ca3..0cc93b0 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/viewmodel/MedicationConfirmViewModel.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationconfirm/viewmodel/MedicationConfirmViewModel.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.waseefakhtar.doseapp.MedicationNotificationService +import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.feature.medicationconfirm.usecase.AddMedicationUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -13,7 +14,8 @@ import javax.inject.Inject @HiltViewModel class MedicationConfirmViewModel @Inject constructor( - private val addMedicationUseCase: AddMedicationUseCase + private val addMedicationUseCase: AddMedicationUseCase, + private val analyticsHelper: AnalyticsHelper ) : ViewModel() { private val _isMedicationSaved = MutableSharedFlow() @@ -26,10 +28,17 @@ class MedicationConfirmViewModel @Inject constructor( for (medication in medications) { val service = MedicationNotificationService(context) - service.scheduleNotification(medication) + service.scheduleNotification( + medication = medication, + analyticsHelper = analyticsHelper + ) } _isMedicationSaved.emit(medicationAdded) } } + + fun logEvent(eventName: String) { + analyticsHelper.logEvent(eventName = eventName) + } } diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/MedicationDetailRoute.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/MedicationDetailRoute.kt index 6c6d119..875d4c8 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/MedicationDetailRoute.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/MedicationDetailRoute.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.waseefakhtar.doseapp.R import com.waseefakhtar.doseapp.analytics.AnalyticsEvents -import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.extension.toFormattedDateString import com.waseefakhtar.doseapp.extension.toFormattedTimeString @@ -70,7 +69,7 @@ fun MedicationDetailScreen( var isSkippedTapped by remember { mutableStateOf(!medication.medicationTaken) } val context = LocalContext.current - val analyticsHelper = AnalyticsHelper.getInstance(context) + Scaffold( topBar = { TopAppBar( @@ -79,7 +78,7 @@ fun MedicationDetailScreen( navigationIcon = { FloatingActionButton( onClick = { - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_DETAIL_ON_BACK_CLICKED) + viewModel.logEvent(AnalyticsEvents.MEDICATION_DETAIL_ON_BACK_CLICKED) onBackClicked() }, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp) @@ -109,7 +108,7 @@ fun MedicationDetailScreen( if (isTakenTapped) { isSkippedTapped = false } - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_DETAIL_TAKEN_CLICKED) + viewModel.logEvent(AnalyticsEvents.MEDICATION_DETAIL_TAKEN_CLICKED) viewModel.updateMedication(medication, isTakenTapped) } ) { @@ -127,7 +126,7 @@ fun MedicationDetailScreen( if (isSkippedTapped) { isTakenTapped = false } - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_DETAIL_SKIPPED_CLICKED) + viewModel.logEvent(AnalyticsEvents.MEDICATION_DETAIL_SKIPPED_CLICKED) viewModel.updateMedication(medication, isTakenTapped) } ) { @@ -144,7 +143,7 @@ fun MedicationDetailScreen( .padding(vertical = 16.dp) .height(56.dp), onClick = { - analyticsHelper.logEvent(AnalyticsEvents.MEDICATION_DETAIL_DONE_CLICKED) + viewModel.logEvent(AnalyticsEvents.MEDICATION_DETAIL_DONE_CLICKED) showSnackbar(context.getString(R.string.medication_logged)) onBackClicked() }, diff --git a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/viewmodel/MedicationDetailViewModel.kt b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/viewmodel/MedicationDetailViewModel.kt index 0984b5d..6b43d16 100644 --- a/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/viewmodel/MedicationDetailViewModel.kt +++ b/app/src/main/java/com/waseefakhtar/doseapp/feature/medicationdetail/viewmodel/MedicationDetailViewModel.kt @@ -2,6 +2,7 @@ package com.waseefakhtar.doseapp.feature.medicationdetail.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.waseefakhtar.doseapp.analytics.AnalyticsHelper import com.waseefakhtar.doseapp.domain.model.Medication import com.waseefakhtar.doseapp.feature.home.usecase.UpdateMedicationUseCase import dagger.hilt.android.lifecycle.HiltViewModel @@ -10,7 +11,8 @@ import javax.inject.Inject @HiltViewModel class MedicationDetailViewModel @Inject constructor( - private val updateMedicationUseCase: UpdateMedicationUseCase + private val updateMedicationUseCase: UpdateMedicationUseCase, + private val analyticsHelper: AnalyticsHelper ) : ViewModel() { fun updateMedication(medication: Medication, isMedicationTaken: Boolean) { @@ -18,4 +20,8 @@ class MedicationDetailViewModel @Inject constructor( updateMedicationUseCase.updateMedication(medication.copy(medicationTaken = isMedicationTaken)) } } + + fun logEvent(eventName: String) { + analyticsHelper.logEvent(eventName = eventName) + } }