refactor: move the drawer collapse store into commons

The last of the Context-bound preference stores. It already took a
DataStore; what held it in amethyst was DrawerSectionId, which sat in
DrawerSections.kt alongside the drawer's actual layout — `Res` strings,
MaterialSymbols, android.os.Build.

The enum and its name codec move to commons/model/navigation, next to
NavBarItem, which the codec's own KDoc already said it mirrors. The layout
stays where it is; only the 32 lines that were pure data moved.

Its 9 tests move to commons with it.

Also adds the `kotlinx.coroutines.IO` import the signer permission store
needed. The JVM compile does not need it — Dispatchers.IO is a JVM
property — so this only surfaced in compileCommonMainKotlinMetadata, which
is the check that speaks for iOS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L
This commit is contained in:
Claude
2026-09-24 15:06:31 +00:00
parent e1fa25ca72
commit deed142586
10 changed files with 69 additions and 45 deletions
@@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
import com.vitorpamplona.amethyst.commons.model.preferences.BuzzAttestationStore
import com.vitorpamplona.amethyst.commons.model.preferences.BuzzChannelStarStore
import com.vitorpamplona.amethyst.commons.model.preferences.BuzzWorkspaceStore
import com.vitorpamplona.amethyst.commons.model.preferences.DrawerSectionCollapsePreferences
import com.vitorpamplona.amethyst.commons.model.preferences.NamecoinSettingsStore
import com.vitorpamplona.amethyst.commons.model.preferences.OtsSettingsStore
import com.vitorpamplona.amethyst.commons.model.preferences.RelayGroupDeletionStore
@@ -72,7 +73,6 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever
import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences
import com.vitorpamplona.amethyst.model.preferences.DrawerSectionCollapsePreferences
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
import com.vitorpamplona.amethyst.model.torState.AccountsTorStateConnector
@@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
@@ -25,8 +25,8 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerItemVisibility
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
import com.vitorpamplona.amethyst.commons.model.navigation.navBarItemsFromNames
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.drawer_section_create
import com.vitorpamplona.amethyst.commons.resources.drawer_section_feeds
@@ -69,38 +69,6 @@ data class DrawerSection(
* copied — a `DrawerSections.map { it.copy(...) }` would silently defeat an `===` check, with no
* compile error and nothing to fail a test.
*/
enum class DrawerSectionId {
YOU,
NAVIGATE,
FEEDS,
/** Composer entry points. Carries no catalog destinations, so nothing in it is configurable. */
CREATE,
/** Also renders the relay-status row, which isn't a catalog destination (it shows a live counter). */
SYSTEM,
}
private val DrawerSectionIdsByName = DrawerSectionId.entries.associateBy { it.name }
/**
* Parses the persisted names of the headings the user has collapsed, silently dropping any this
* build doesn't know. Mirrors [com.vitorpamplona.amethyst.commons.model.navigation.navBarItemsFromNames]:
* names rather than ordinals, so reordering this enum renames nothing by accident, and a value left
* by a build with one more section costs that heading rather than the whole read.
*
* The stored set holds the **collapsed** headings rather than the expanded ones, for the same reason
* [DrawerItemVisibility] stores the hidden rows: a heading nobody has ever collapsed simply isn't in
* the set, so a section added in a later release opens expanded for everyone with no migration.
*/
fun drawerSectionIdsFromNames(names: Collection<String>): Set<DrawerSectionId> = names.mapNotNullTo(mutableSetOf()) { DrawerSectionIdsByName[it] }
/**
* The inverse of [drawerSectionIdsFromNames]. Unlike the NavBarItem codec this returns an unsorted
* Set rather than a sorted List: the destination is a DataStore string set, whose equality is
* already order-independent, so there is no serialized form to keep deterministic.
*/
fun Set<DrawerSectionId>.toNames(): Set<String> = mapTo(mutableSetOf()) { it.name }
private val DrawerNavigateItems: List<NavBarItem> =
listOf(
@@ -45,6 +45,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.MandatoryDrawerItems
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.drawer_settings
@@ -65,7 +66,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSection
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionId
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.navigation
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerItemVisibility
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionId
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.DrawerSettingsState
@@ -20,13 +20,13 @@
*/
package com.vitorpamplona.amethyst.navigation
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.MandatoryDrawerItems
import com.vitorpamplona.amethyst.commons.model.navigation.drawerSectionIdsFromNames
import com.vitorpamplona.amethyst.commons.model.navigation.toNames
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionId
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections
import com.vitorpamplona.amethyst.ui.navigation.drawer.SdkGatedDrawerItems
import com.vitorpamplona.amethyst.ui.navigation.drawer.drawerSectionIdsFromNames
import com.vitorpamplona.amethyst.ui.navigation.drawer.toNames
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.utils.sha256.sha256
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.model.navigation
enum class DrawerSectionId {
YOU,
NAVIGATE,
FEEDS,
/** Composer entry points. Carries no catalog destinations, so nothing in it is configurable. */
CREATE,
/** Also renders the relay-status row, which isn't a catalog destination (it shows a live counter). */
SYSTEM,
}
private val DrawerSectionIdsByName = DrawerSectionId.entries.associateBy { it.name }
/**
* Parses the persisted names of the headings the user has collapsed, silently dropping any this
* build doesn't know. Mirrors [com.vitorpamplona.amethyst.commons.model.navigation.navBarItemsFromNames]:
* names rather than ordinals, so reordering this enum renames nothing by accident, and a value left
* by a build with one more section costs that heading rather than the whole read.
*
* The stored set holds the **collapsed** headings rather than the expanded ones, for the same reason
* [DrawerItemVisibility] stores the hidden rows: a heading nobody has ever collapsed simply isn't in
* the set, so a section added in a later release opens expanded for everyone with no migration.
*/
fun drawerSectionIdsFromNames(names: Collection<String>): Set<DrawerSectionId> = names.mapNotNullTo(mutableSetOf()) { DrawerSectionIdsByName[it] }
/**
* The inverse of [drawerSectionIdsFromNames]. Unlike the NavBarItem codec this returns an unsorted
* Set rather than a sorted List: the destination is a DataStore string set, whose equality is
* already order-independent, so there is no serialized form to keep deterministic.
*/
fun Set<DrawerSectionId>.toNames(): Set<String> = mapTo(mutableSetOf()) { it.name }
@@ -18,16 +18,16 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.preferences
package com.vitorpamplona.amethyst.commons.model.preferences
import androidx.compose.runtime.Stable
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringSetPreferencesKey
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionId
import com.vitorpamplona.amethyst.ui.navigation.drawer.drawerSectionIdsFromNames
import com.vitorpamplona.amethyst.ui.navigation.drawer.toNames
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.drawerSectionIdsFromNames
import com.vitorpamplona.amethyst.commons.model.navigation.toNames
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,12 +18,12 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.preferences
package com.vitorpamplona.amethyst.commons.model.preferences
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionId
import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel