Skip to content

Add dashboard tools personalization mode#4392

Merged
frett merged 13 commits into
developfrom
dashboardToolsPersonalization
May 4, 2026
Merged

Add dashboard tools personalization mode#4392
frett merged 13 commits into
developfrom
dashboardToolsPersonalization

Conversation

@frett

@frett frett commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add `CONFIG_UI_DASHBOARD_PERSONALIZATION_ENABLED` feature flag to gate personalization UI
  • Add personalization mode to `ToolsPresenter`, `FilteredToolsFlowProducer`, and `ToolFiltersStateProducer`
  • Add personalization toggle and mode-aware header to `ToolsLayout`
  • Rename country setting key and add `getPersonalizationCountryFlow()` to `Settings`
  • Remove variant filtering from `FilteredToolsFlowProducer`
  • Introduce `SyncTaskRegistry` to coordinate sync tasks across nested Circuit presenters
  • Consolidate the `featured` + `default_order` tool order endpoints into a single `default_order` call with an optional country parameter, renaming `syncPersonalizedTools` → `syncToolOrder`
  • Wire tool order sync into `DashboardPresenter` and `ToolsPresenter` via `SyncTaskRegistry`

Test plan

  • Verify personalization toggle appears and toggles between Personalization/All Tools modes
  • Verify filtered tools list updates correctly in each mode
  • Verify tool order sync fires on presenter start and re-fires when the language filter changes
  • Run unit tests: `./gradlew :app:testProductionDebugUnitTest`

🤖 Generated with Claude Code

@frett frett force-pushed the dashboardToolsPersonalization branch from e0d27cd to bf86a48 Compare May 1, 2026 20:31
@frett frett requested a review from tjohnson009 May 1, 2026 20:31
@frett frett marked this pull request as ready for review May 1, 2026 20:31
@frett frett force-pushed the dashboardToolsPersonalization branch from bf86a48 to 73b73e0 Compare May 1, 2026 20:48
frett and others added 12 commits May 1, 2026 16:08
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a Mode enum (PERSONALIZATION / ALL_TOOLS) to ToolsPresenter,
driven by the CONFIG_UI_DASHBOARD_PERSONALIZATION_ENABLED remote config flag.
Spotlight tools are hidden when personalization is enabled and mode is ALL_TOOLS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cuit presenters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lOrder API call

Merges the two-endpoint pattern (featured + default_order) into a single
default_order endpoint with an optional country parameter, and renames
syncPersonalizedTools → syncToolOrder throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…yncTaskRegistry

DashboardPresenter now registers its syncData task with the SyncTaskRegistry
and exposes the registry on CircuitContext so nested presenters can participate.
ToolsPresenter registers a syncToolOrder task against that registry, replacing
the previous direct rememberSyncTracker usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@frett frett force-pushed the dashboardToolsPersonalization branch from f7e25a1 to 42ee3b7 Compare May 1, 2026 22:12
@codecov

codecov Bot commented May 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.79245% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.02%. Comparing base (4f91040) to head (6632a9d).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
.../src/main/kotlin/org/cru/godtools/base/Settings.kt 16.66% 5 Missing ⚠️
.../cru/godtools/ui/dashboard/tools/ToolsPresenter.kt 87.09% 0 Missing and 4 partials ⚠️
...org/cru/godtools/ui/dashboard/tools/ToolsLayout.kt 94.44% 0 Missing and 3 partials ⚠️
...ols/ui/dashboard/tools/ToolFiltersStateProducer.kt 75.00% 0 Missing and 2 partials ⚠️
...otlin/org/cru/godtools/sync/GodToolsSyncService.kt 0.00% 2 Missing ⚠️
...kotlin/org/cru/godtools/sync/task/ToolSyncTasks.kt 83.33% 0 Missing and 2 partials ⚠️
...rg/cru/godtools/ui/dashboard/DashboardPresenter.kt 90.00% 0 Missing and 1 partial ⚠️
...i/src/main/kotlin/org/cru/godtools/api/ToolsApi.kt 0.00% 1 Missing ⚠️
...se/src/main/kotlin/org/cru/godtools/base/Config.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4392      +/-   ##
===========================================
+ Coverage    50.20%   51.02%   +0.81%     
===========================================
  Files          447      449       +2     
  Lines        11996    12082      +86     
  Branches      2081     2094      +13     
===========================================
+ Hits          6023     6165     +142     
+ Misses        5370     5305      -65     
- Partials       603      612       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@frett frett force-pushed the dashboardToolsPersonalization branch from 42ee3b7 to 6632a9d Compare May 1, 2026 22:31
import org.cru.godtools.base.ui.circuit.screen.dashboard.page.LessonsScreen
import org.cru.godtools.base.ui.circuit.screen.dashboard.page.ToolsScreen
import org.cru.godtools.base.ui.compose.LocalEventBus
import org.cru.godtools.base.ui.theme.GodToolsTheme

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was to remove the blue bar at the top of the screen right? If not, then I'm confused with what's happening here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, we kept the dashboard app bar, but removed the blue theme from it

// region Personalization Settings
fun getCountrySettingFlow() = getPersonalizationCountryFlow()

fun getPersonalizationCountryFlow() = dataStorePreferences.data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why did you decide to make a function out of this? Readability or ease of finding it in the future?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was renaming it to reference that it's related to personalization only, I kept the old function around to prevent the change from breaking code you were working on. At a future time I was going to remove the old method.

else -> toolsRepository.getNormalToolsFlowByLanguage(language)
}

val defaultVariantsFlow = toolsRepository.getMetaToolsFlow()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure what a metatool is, and I think that might be hindering me from getting whats going on here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wait... maybe I do know what that is. Is this how the 4 Spiritual Laws booklet relates to the KGP or Would You Like To Know God Personally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

metatools are a way of grouping tools together that are the same content just presented towards different audiences. You can see them if you open tool details of 4 laws or kgp, it's on the "Versions" tab.

We previously had been hiding those other versions from the all tools list, but part of the requested changes was to make them available on the all tools UI


@Composable
override fun present(): UiState {
val isPersonalizationEnabled = rememberSaveable {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rememberSaveable is so that it persists across refreshes and activity destruction / recreation right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, we want to load whether personalization is enabled/disabled at an app level when the screen is launched, and then prevent it from causing the UI to jump if the setting changes while the user is viewing the screen.

So, we load it once and store it via rememberSaveable. It will stay in the same mode while the screen is up and then re-evaluate if you relaunch the app/dashboard fresh

fun getFlow(mode: Mode, category: String? = null, language: Locale? = null): Flow<List<Tool>> {
val baseFlow = when {
mode == Mode.PERSONALIZATION -> {
val languageFlow = if (language != null) flowOf(language) else settings.appLanguageFlow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this to take into account if someone sets a personalization country and the language is different then their app language and we need to pull the tools for that specific language? Want to make sure I understand what is happening here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

on the Dashboard they want the list of tools/lessons to take the language filter into account and default to the appLanguage

import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState.Mode

class FakeToolFiltersStateProducer : ToolFiltersStateProducer {
val filters = MutableStateFlow(Filters())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand why we need a fakeToolFiltersStateProducer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a test utility to help isolate the individual components for testing, a fake is similar to a mock.

I want to start using Fakes when the object being faked/mocked has a composable function we need to override, the mockposable dependency we've been using to mock objects like this tends to delay uptake of new Kotlin versions due to compatibility.

// region Personalized Tools
private val personalizedToolsMutex = MutexMap()
// region Tool Order
private val toolOrderMutex = MutexMap()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I had to look this up. It seems similar to a semaphore but not quite the same as the gate analogy that was for a semaphore

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

a Mutex is effectively a single permit semaphore

@tjohnson009 tjohnson009 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I took a look at just about everything. I had more questions but they can definitely be for another time.

@frett frett merged commit b283b28 into develop May 4, 2026
13 checks passed
@frett frett deleted the dashboardToolsPersonalization branch May 4, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants