Updated on 2026-08-14
This commit is contained in:
parent
a9bca131c3
commit
b4ae0c1547
14 changed files with 533 additions and 465 deletions
77
domain/core/CLAUDE.md
Normal file
77
domain/core/CLAUDE.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# domain/core
|
||||
|
||||
Cross-cutting domain utilities for async data loading, error handling, and reactive streams. Not business logic — foundational abstractions used across all domain modules.
|
||||
|
||||
## LCE (Loading-Content-Error) Pattern
|
||||
|
||||
`Lce<E, C>` — sealed class representing async operation state:
|
||||
- `Loading(partialContent?)` — in progress, may carry partial data
|
||||
- `Content(content)` — success
|
||||
- `Error(error)` — failure with typed error
|
||||
|
||||
Key APIs:
|
||||
- `lce { }` builder — executes block in `LceRaise` context with Arrow's Raise DSL for typed error handling
|
||||
- `lceFlow { }` builder — creates `LceFlow<E, C>` (alias for `Flow<Lce<E, C>>`) via channel-based producer DSL
|
||||
- `LceRaise.bind()` — extracts content from Lce/Either or short-circuits on error
|
||||
- Extensions: `fold()`, `map()`, `mapError()`, `toLce()`, `toEither()`
|
||||
|
||||
## Flow Packaging
|
||||
|
||||
A pattern for complex data streams where work on a single flow is split into three logically separate components: **Producer** (creation), **Supplier** (delivery/caching), and **Fetcher** (refresh). Use it only when you need flexibility in creating, reusing, fetching, and updating a data stream (e.g., network status). Do NOT use for simple cases like reading preferences.
|
||||
|
||||
### FlowProducer
|
||||
|
||||
`FlowProducer<Data>` — creates the data flow. Implement:
|
||||
- `fallback: Data` — emitted when the flow throws an exception
|
||||
- `produce(): Flow<Data>` — the actual flow creation logic
|
||||
|
||||
Built-in `produceWithFallback()` catches errors, emits `fallback`, waits 2s, then retries — keeping the flow alive for subscribers.
|
||||
|
||||
`FlowProducer.Factory<Params, Producer>` — creates a Producer from params. Typically implemented via Hilt `@AssistedFactory`.
|
||||
|
||||
**Implementation pattern:**
|
||||
1. Define interface extending `FlowProducer<Data>` with inner `Params` data class and `Factory` interface
|
||||
2. Create `Default*Producer` with `@AssistedInject` constructor taking `@Assisted params` + dependencies
|
||||
3. Override `fallback` and `produce()`
|
||||
4. Declare inner `@AssistedFactory` interface extending the Producer's Factory
|
||||
|
||||
### FlowSupplier / FlowCachingSupplier
|
||||
|
||||
`FlowSupplier<Params, Data>` — delivers a flow by params via `operator fun invoke(params): Flow<Data>`. Also provides `getSyncOrNull(params, timeout)` for one-shot access.
|
||||
|
||||
`FlowCachingSupplier<Producer, Params, Data>` — abstract implementation that caches flows by key. Implement:
|
||||
- `factory: FlowProducer.Factory` — to create producers
|
||||
- `keyCreator: (Params) -> String` — to generate cache keys
|
||||
|
||||
Behavior: returns cached flow if exists, otherwise creates via `factory.create(params).produceWithFallback()`, caches it, and auto-evicts on terminal exception.
|
||||
|
||||
**Implementation pattern:**
|
||||
1. Define abstract class extending `FlowCachingSupplier` with `factory` and `keyCreator` in constructor
|
||||
2. In DI module, create anonymous subclass providing the factory (injected) and keyCreator lambda
|
||||
|
||||
### FlowFetcher
|
||||
|
||||
`FlowFetcher<Params>` — triggers data refresh, returns `Either<Throwable, Unit>`. Typically updates a store/data source, causing the Producer's flow to re-emit.
|
||||
|
||||
**Implementation pattern:**
|
||||
1. Define interface extending `FlowFetcher<Params>` with inner `Params` data class
|
||||
2. Create `Default*Fetcher` with `@Inject` constructor, override `invoke` wrapping logic in `Either.catch { }`, handle errors with `.onLeft { }`
|
||||
|
||||
### Testing
|
||||
|
||||
- **FlowProducer**: test flow creation logic, params usage, emission behavior, exception handling
|
||||
- **FlowFetcher**: test successful update path and error path (exception thrown)
|
||||
|
||||
## Chain Processing
|
||||
|
||||
- `Chain<E, R>` / `ResultChain<E, R>` — single operation in a chain, works with `Either<E, R>`
|
||||
- `ChainProcessor<E, R>` — folds chains sequentially, stops on first error
|
||||
|
||||
## Error Types
|
||||
|
||||
- `DataError` — sealed domain error hierarchy: `NetworkError.NoInternetConnection`, `UserWalletError.WrongUserWallet`
|
||||
|
||||
## Either Extensions
|
||||
|
||||
- `Either.catchOn(dispatcher, block)` — executes on dispatcher, catches exceptions
|
||||
- `eitherOn(dispatcher, block)` — Raise DSL block on specified dispatcher
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
package com.tangem.domain.wallets.legacy
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface UserWalletsListManager {
|
||||
|
||||
/**
|
||||
* Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable]
|
||||
* */
|
||||
val isLockable: Boolean
|
||||
|
||||
/** [Flow] with all saved [UserWallet]s updates */
|
||||
val userWallets: Flow<List<UserWallet>>
|
||||
|
||||
/** Count saved wallets updates */
|
||||
val savedWalletsCount: Flow<Int>
|
||||
|
||||
/** [Flow] with selected [UserWallet] updates */
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
val selectedUserWallet: Flow<UserWallet>
|
||||
|
||||
/** [List] with all saved [UserWallet]s updates */
|
||||
val userWalletsSync: List<UserWallet>
|
||||
|
||||
/** Selected [UserWallet] */
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
val selectedUserWalletSync: UserWallet?
|
||||
|
||||
/** Indicates that the [UserWalletsListManager] contains at least one saved [UserWallet] */
|
||||
val hasUserWallets: Boolean
|
||||
|
||||
/** Count of saved user wallets */
|
||||
val walletsCount: Int
|
||||
|
||||
/**
|
||||
* Set [UserWallet] with provided [UserWalletId] as selected
|
||||
*
|
||||
* @param userWalletId [UserWalletId] of [UserWallet] which must be selected
|
||||
*
|
||||
* @return [CompletionResult.Success] with selected [UserWallet] or [CompletionResult.Failure] with
|
||||
* [NoSuchElementException] if [UserWallet] with [userWalletId] not found
|
||||
*/
|
||||
suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
/**
|
||||
* Save provided user wallet and set it as selected
|
||||
*
|
||||
* @param userWallet [UserWallet] to save
|
||||
* @param canOverride If false, then terminate with [UserWalletsListError.WalletAlreadySaved] when user tries
|
||||
* to save an already saved card
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
*/
|
||||
suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Same as [save] but not change selected user wallet ID and not terminate with
|
||||
* [UserWalletsListError.WalletAlreadySaved] if [UserWallet] already saved.
|
||||
* Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId].
|
||||
*
|
||||
* @param userWalletId update [UserWallet] with that [UserWalletId]
|
||||
* @param update lambda that receives stored [UserWallet] and returns updated [UserWallet]
|
||||
*
|
||||
* @return [CompletionResult.Success] with updated [UserWallet] or [CompletionResult.Failure] with
|
||||
* [NoSuchElementException] if [UserWallet] with [userWalletId] not found
|
||||
*/
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet>
|
||||
|
||||
/**
|
||||
* Delete saved [UserWallet]s with provided [UserWalletId]s.
|
||||
* Sets [isLocked] as true if [userWallets] is empty or if all [userWallets] are locked.
|
||||
*
|
||||
* @param userWalletIds [UserWalletId]s of [UserWallet]s which must be deleted
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
*/
|
||||
suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Clear all saved [UserWallet]s and set [isLocked] as true
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
*/
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Get [UserWallet] with provided [UserWalletId]
|
||||
*
|
||||
* @return [CompletionResult.Success] with found [UserWallet] or [CompletionResult.Failure] with
|
||||
* [NoSuchElementException] if [UserWallet] with [userWalletId] not found
|
||||
*/
|
||||
suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
interface Lockable : UserWalletsListManager {
|
||||
|
||||
/**
|
||||
* Indicates that all [UserWallet]s is locked
|
||||
*
|
||||
* @see [isLocked]
|
||||
* @see [UserWallet.isLocked]
|
||||
*/
|
||||
val lockedState: Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Indicates that all [UserWallet]s is locked. Sync version.
|
||||
*
|
||||
* @see [lockedState]
|
||||
* @see [UserWallet.isLocked]
|
||||
*/
|
||||
val isLocked: Boolean
|
||||
|
||||
/**
|
||||
* Receive saved [UserWallet]s, populate [userWallets] flow with it and set [lockedState] as false.
|
||||
*
|
||||
* @param type Defines the behavior of the operation.
|
||||
*
|
||||
* @return [CompletionResult] of operation, with selected [UserWallet]
|
||||
* or null if there is no selected [UserWallet]
|
||||
*/
|
||||
suspend fun unlock(type: UnlockType): CompletionResult<UserWallet>
|
||||
|
||||
/** Remove [UserWallet]s from [userWallets] and set [lockedState] as true */
|
||||
fun lock()
|
||||
|
||||
/**
|
||||
* Defines the behavior of the [unlock] operation.
|
||||
* */
|
||||
enum class UnlockType {
|
||||
/**
|
||||
* Ensures that all stored [UserWallet]s are unlocked,
|
||||
* or throws [UserWalletsListError.NotAllUserWalletsUnlocked].
|
||||
*
|
||||
* In this type [selectedUserWallet] is either a previously selected [UserWallet] or the first stored
|
||||
* [UserWallet].
|
||||
* */
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* Ensures that at least one stored [UserWallet] is unlocked,
|
||||
* or throws [UserWalletsListError.NoUserWalletSelected].
|
||||
*
|
||||
* In this type [selectedUserWallet] is the first stored and unlocked [UserWallet].
|
||||
* */
|
||||
ANY,
|
||||
|
||||
/**
|
||||
* Same as [ALL] type, but this type can not change [selectedUserWallet] while unlocking.
|
||||
* */
|
||||
ALL_WITHOUT_SELECT,
|
||||
}
|
||||
}
|
||||
|
||||
// For provider
|
||||
companion object
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue