Skip to content
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
32 changes: 18 additions & 14 deletions app/src/main/java/com/jodli/coffeeshottimer/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,16 @@ fun EspressoShotTrackerApp(mainActivityViewModel: MainActivityViewModel) {
val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE

// Determine if navigation should be shown
val showNavigation = when (currentRoute) {
NavigationDestinations.RecordShot.route,
NavigationDestinations.ShotHistory.route,
NavigationDestinations.BeanManagement.route,
NavigationDestinations.More.route -> true
// Strip query parameters for route matching
val currentRouteBase = currentRoute?.substringBefore('?')
val showNavigation = when (currentRouteBase) {
NavigationDestinations.RecordShot.baseRoute,
NavigationDestinations.ShotHistory.baseRoute,
NavigationDestinations.BeanManagement.baseRoute,
NavigationDestinations.More.baseRoute -> true
// Hide navigation during onboarding
NavigationDestinations.OnboardingIntroduction.route,
NavigationDestinations.OnboardingEquipmentSetup.route -> false
NavigationDestinations.OnboardingIntroduction.baseRoute,
NavigationDestinations.OnboardingEquipmentSetup.baseRoute -> false
else -> false
}

Expand Down Expand Up @@ -138,13 +140,15 @@ fun EspressoShotTrackerApp(mainActivityViewModel: MainActivityViewModel) {
val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE

val showNavigation = when (currentRoute) {
NavigationDestinations.RecordShot.route,
NavigationDestinations.ShotHistory.route,
NavigationDestinations.BeanManagement.route,
NavigationDestinations.More.route -> true
NavigationDestinations.OnboardingIntroduction.route,
NavigationDestinations.OnboardingEquipmentSetup.route -> false
// Strip query parameters for route matching
val currentRouteBase = currentRoute?.substringBefore('?')
val showNavigation = when (currentRouteBase) {
NavigationDestinations.RecordShot.baseRoute,
NavigationDestinations.ShotHistory.baseRoute,
NavigationDestinations.BeanManagement.baseRoute,
NavigationDestinations.More.baseRoute -> true
NavigationDestinations.OnboardingIntroduction.baseRoute,
NavigationDestinations.OnboardingEquipmentSetup.baseRoute -> false
else -> false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,6 @@ interface BeanDao {
@Delete
suspend fun deleteBean(bean: Bean)

/**
* Update the last grinder setting for a specific bean.
*/
@Query("UPDATE beans SET lastGrinderSetting = :grinderSetting WHERE id = :beanId")
suspend fun updateLastGrinderSetting(beanId: String, grinderSetting: String)

/**
* Set a bean as active/inactive.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ data class Bean(
val roastDate: LocalDate,
val notes: String = "",
val isActive: Boolean = true,
@Deprecated(
message = "Grinder settings are now queried from shots table for single source of truth.",
level = DeprecationLevel.WARNING
)
val lastGrinderSetting: String? = null,
val photoPath: String? = null,
val createdAt: LocalDateTime = LocalDateTime.now()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,42 +207,6 @@ class BeanRepository @Inject constructor(
}
}

/**
* Update the last grinder setting for a specific bean.
* @param beanId The ID of the bean
* @param grinderSetting The grinder setting to save
* @return Result indicating success or failure
*/
suspend fun updateLastGrinderSetting(beanId: String, grinderSetting: String): Result<Unit> {
return try {
if (beanId.isBlank()) {
return Result.failure(RepositoryException.ValidationError("Bean ID cannot be empty"))
}
if (grinderSetting.isBlank()) {
return Result.failure(RepositoryException.ValidationError("Grinder setting cannot be empty"))
}
if (grinderSetting.length > 50) {
return Result.failure(
RepositoryException.ValidationError("Grinder setting cannot exceed 50 characters")
)
}

// Check if bean exists
val existingBean = beanDao.getBeanById(beanId)
?: return Result.failure(RepositoryException.NotFoundError("Bean not found"))

beanDao.updateLastGrinderSetting(beanId, grinderSetting)
Result.success(Unit)
} catch (exception: Exception) {
Result.failure(
RepositoryException.DatabaseError(
"Failed to update grinder setting",
exception
)
)
}
}

/**
* Set a bean as active or inactive.
* @param beanId The ID of the bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,6 @@ class ShotRepository @Inject constructor(
// Record the shot
shotDao.insertShot(shot)

// Update the bean's last grinder setting with the successful settings
beanDao.updateLastGrinderSetting(shot.beanId, shot.grinderSetting)

Result.success(Unit)
} catch (exception: Exception) {
Result.failure(RepositoryException.DatabaseError("Failed to record shot", exception))
Expand Down Expand Up @@ -550,22 +547,17 @@ class ShotRepository @Inject constructor(
}

/**
* Get suggested grinder setting for a bean based on last successful shot.
* Get suggested grinder setting for a bean from the most recent shot.
* Single source of truth: always queries from shots table.
* @param beanId The ID of the bean
* @return Result containing suggested grinder setting or null if no history
* @return Result containing suggested grinder setting or null if no shots
*/
suspend fun getSuggestedGrinderSetting(beanId: String): Result<String?> {
return try {
if (beanId.isBlank()) {
Result.failure(RepositoryException.ValidationError("Bean ID cannot be empty"))
} else {
// First check the bean's last grinder setting
val bean = beanDao.getBeanById(beanId)
if (bean?.lastGrinderSetting != null) {
return Result.success(bean.lastGrinderSetting)
}

// If no saved setting, get from last shot
// Get grinder setting from most recent shot
val lastShot = shotDao.getLastShotForBean(beanId)
Result.success(lastShot?.grinderSetting)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class DatabasePopulator @Inject constructor(
roastDate = LocalDate.now().minusDays((3..20).random().toLong()),
notes = profile.notes,
isActive = true,
lastGrinderSetting = profile.grinderSetting,
lastGrinderSetting = null, // Deprecated: kept for DB compatibility, not used
createdAt = LocalDateTime.now().minusDays(index.toLong())
)
}
Expand Down Expand Up @@ -253,7 +253,7 @@ class DatabasePopulator @Inject constructor(
val extractionTime = (profile.extractionTimeSeconds + timeVariation).coerceIn(15, 50)

// Calculate grinder setting with skill-level consistency
val baseGrinderSetting = bean.lastGrinderSetting?.toDoubleOrNull() ?: 3.0
val baseGrinderSetting = 3.0 // Default grinder setting
val grinderVariation = Random.nextDouble(-skillVariations.grinderVariance, skillVariations.grinderVariance)
val grinderSetting = String.format(
java.util.Locale.ROOT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,35 +120,6 @@ class GetActiveBeansUseCase @Inject constructor(
}
}

/**
* Get active beans that have grinder settings saved.
* Useful for quick shot recording with remembered settings.
* @return Flow of Result containing list of active beans with grinder settings
*/
fun getActiveBeansWithGrinderSettings(): Flow<Result<List<Bean>>> {
return beanRepository.getActiveBeans()
.map { result ->
if (result.isSuccess) {
val beans = result.getOrNull() ?: emptyList()
val beansWithSettings = beans.filter { !it.lastGrinderSetting.isNullOrBlank() }
Result.success(beansWithSettings.sortedByDescending { it.createdAt })
} else {
result
}
}
.catch { exception ->
emit(
Result.failure(
DomainException(
DomainErrorCode.UNKNOWN_ERROR,
"Failed to get active beans with grinder settings",
exception
)
)
)
}
}

/**
* Get count of active beans.
* @return Result containing the count of active beans
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,35 +215,6 @@ class GetBeanHistoryUseCase @Inject constructor(
}
}

/**
* Get beans with grinder settings history.
* Useful for analyzing grinder setting patterns across different beans.
* @return Flow of Result containing list of beans that have grinder settings
*/
fun getBeansWithGrinderSettings(): Flow<Result<List<Bean>>> {
return beanRepository.getAllBeans()
.map { result ->
if (result.isSuccess) {
val beans = result.getOrNull() ?: emptyList()
val beansWithSettings = beans.filter { !it.lastGrinderSetting.isNullOrBlank() }
Result.success(beansWithSettings.sortedByDescending { it.createdAt })
} else {
result
}
}
.catch { exception ->
emit(
Result.failure(
DomainException(
DomainErrorCode.FAILED_TO_GET_ACTIVE_BEANS,
"Failed to get beans with grinder settings",
exception
)
)
)
}
}

/**
* Get bean statistics for historical analysis.
* @return Result containing BeanHistoryStats
Expand All @@ -260,9 +231,6 @@ class GetBeanHistoryUseCase @Inject constructor(
val totalBeans = beans.size
val activeBeans = beans.count { it.isActive }
val inactiveBeans = totalBeans - activeBeans
val beansWithGrinderSettings =
beans.count { !it.lastGrinderSetting.isNullOrBlank() }

val freshBeans = beans.count { it.isFresh() }
val averageDaysSinceRoast = if (beans.isNotEmpty()) {
beans.map { it.daysSinceRoast() }.average()
Expand All @@ -277,7 +245,6 @@ class GetBeanHistoryUseCase @Inject constructor(
totalBeans = totalBeans,
activeBeans = activeBeans,
inactiveBeans = inactiveBeans,
beansWithGrinderSettings = beansWithGrinderSettings,
freshBeans = freshBeans,
averageDaysSinceRoast = averageDaysSinceRoast,
oldestRoastDate = oldestBean?.roastDate,
Expand Down Expand Up @@ -347,7 +314,6 @@ data class BeanHistoryStats(
val totalBeans: Int = 0,
val activeBeans: Int = 0,
val inactiveBeans: Int = 0,
val beansWithGrinderSettings: Int = 0,
val freshBeans: Int = 0,
val averageDaysSinceRoast: Double = 0.0,
val oldestRoastDate: LocalDate? = null,
Expand Down
Loading
Loading