diff --git a/.claude/settings.local.template.json b/.claude/settings.local.template.json
new file mode 100644
index 000000000..bd209fde4
--- /dev/null
+++ b/.claude/settings.local.template.json
@@ -0,0 +1,26 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash",
+ "Read",
+ "Edit",
+ "Write",
+ "WebFetch",
+ "mcp__ide__getDiagnostics",
+ "WebSearch",
+ "mcp__github__pull_request_read",
+ "mcp__github__search_pull_requests",
+ "mcp__github__list_pull_requests",
+ "mcp__github__get_me",
+ "mcp__github__get_file_contents"
+ ],
+ "deny": [],
+ "ask": [
+ "Bash(rm -rf:*)",
+ "Bash(git commit:*)",
+ "Bash(git push:*)"
+ ],
+ "additionalDirectories": [
+ ]
+ }
+}
diff --git a/.cursor/notes/libs.md b/.cursor/notes/libs.md
new file mode 100644
index 000000000..8bf348e91
--- /dev/null
+++ b/.cursor/notes/libs.md
@@ -0,0 +1,162 @@
+# Android Libraries Documentation Reference
+
+This document provides a comprehensive reference for all libraries used in the bitkit-android project, including their documentation links and key usage patterns.
+
+## Core Android Libraries
+
+### Kotlin
+- **Documentation**: https://kotlinlang.org/docs/
+- **API Reference**: https://kotlinlang.org/api/latest/jvm/stdlib/
+
+### Android Core
+- **Core KTX**: https://developer.android.com/kotlin/ktx
+- **AppCompat**: https://developer.android.com/jetpack/androidx/releases/appcompat
+- **Material Design**: https://material.io/develop/android
+- **Core Splashscreen**: https://developer.android.com/develop/ui/views/launch/splash-screen
+
+## UI Framework
+
+### Jetpack Compose
+- **Main Documentation**: https://developer.android.com/jetpack/compose
+- **BOM Mapping**: https://developer.android.com/develop/ui/compose/bom/bom-mapping
+- **Material 3**: https://developer.android.com/jetpack/compose/designsystems/material3
+- **Navigation**: https://developer.android.com/jetpack/compose/navigation
+- **Tooling**: https://developer.android.com/jetpack/compose/tooling
+
+### Accompanist
+- **Documentation**: https://google.github.io/accompanist/
+- **Permissions**: https://google.github.io/accompanist/permissions/
+- **Pager Indicators**: https://google.github.io/accompanist/pager/
+
+### Layout
+- **ConstraintLayout Compose**: https://developer.android.com/jetpack/compose/layouts/constraintlayout
+
+## Architecture & Dependency Injection
+
+### Hilt
+- **Documentation**: https://dagger.dev/hilt/
+- **Android Guide**: https://developer.android.com/training/dependency-injection/hilt-android
+- **Compose Integration**: https://developer.android.com/jetpack/compose/libraries#hilt
+
+### Lifecycle Components
+- **Documentation**: https://developer.android.com/jetpack/androidx/releases/lifecycle
+- **ViewModel**: https://developer.android.com/topic/libraries/architecture/viewmodel
+- **Compose Integration**: https://developer.android.com/jetpack/compose/libraries#viewmodel
+
+### Data Persistence
+- **DataStore Preferences**: https://developer.android.com/topic/libraries/architecture/datastore
+- **Room**: https://developer.android.com/training/data-storage/room
+
+## Networking & Serialization
+
+### Ktor
+- **Documentation**: https://ktor.io/docs/
+- **Client Documentation**: https://ktor.io/docs/getting-started-ktor-client.html
+- **Android Guide**: https://ktor.io/docs/client-engines.html#android
+
+### Serialization
+- **Kotlinx Serialization**: https://kotlinlang.org/docs/serialization.html
+- **Protobuf**: https://protobuf.dev/
+
+## Bitcoin & Lightning Network
+
+### LDK Node Android
+- **GitHub**: https://github.com/synonymdev/ldk-node (Fork)
+- **Upstream Docs**: https://lightningdevkit.org/
+- **API Reference**: https://docs.rs/ldk-node/latest/ldk_node/
+
+### Bitkit Core
+- **GitHub**: https://github.com/synonymdev/bitkit-core
+- **Custom Android bindings for Bitcoin operations**
+
+### Cryptography
+- **BouncyCastle**: https://www.bouncycastle.org/java.html
+- **Provider Documentation**: https://www.bouncycastle.org/documentation.html
+
+## Media & Scanning
+
+### Camera
+- **CameraX**: https://developer.android.com/training/camerax
+- **Camera2 API**: https://developer.android.com/training/camerax/architecture
+
+### Barcode/QR Scanning
+- **ZXing**: https://github.com/zxing/zxing
+- **ML Kit Barcode**: https://developers.google.com/ml-kit/vision/barcode-scanning
+
+### Animations
+- **Lottie Compose**: https://airbnb.io/lottie/#/android-compose
+
+## Firebase
+
+### Firebase Platform
+- **Documentation**: https://firebase.google.com/docs/android/setup
+- **Messaging**: https://firebase.google.com/docs/cloud-messaging/android/client
+- **Analytics**: https://firebase.google.com/docs/analytics/get-started?platform=android
+
+## Security & Authentication
+
+### Biometric
+- **Documentation**: https://developer.android.com/jetpack/androidx/releases/biometric
+- **Guide**: https://developer.android.com/training/sign-in/biometric-auth
+
+## Background Processing
+
+### WorkManager
+- **Documentation**: https://developer.android.com/topic/libraries/architecture/workmanager
+- **Hilt Integration**: https://developer.android.com/training/dependency-injection/hilt-jetpack
+
+## Utilities
+
+### Date/Time
+- **Kotlinx DateTime**: https://github.com/Kotlin/kotlinx-datetime
+
+### Charts
+- **Compose Charts**: https://github.com/ehsannarmani/ComposeCharts
+
+### Native Libraries
+- **JNA**: https://github.com/java-native-access/jna
+
+## Testing Libraries
+
+### Unit Testing
+- **JUnit**: https://junit.org/junit4/
+- **Mockito Kotlin**: https://github.com/mockito/mockito-kotlin
+- **Robolectric**: http://robolectric.org/
+- **Turbine**: https://github.com/cashapp/turbine
+
+### Android Testing
+- **Espresso**: https://developer.android.com/training/testing/espresso
+- **Compose Testing**: https://developer.android.com/jetpack/compose/testing
+- **Hilt Testing**: https://developer.android.com/training/dependency-injection/hilt-testing
+
+## Key Configuration Notes
+
+### Compose Compiler Flags
+- StrongSkipping is disabled
+- OptimizeNonSkippingGroups is enabled
+
+### Build Configuration
+- Minimum SDK: 28
+- Target SDK: 35
+- Kotlin JVM Target: 11
+- Compose BOM manages all Compose library versions
+
+## Development Guidelines
+
+### When Adding New Libraries
+1. Check if functionality exists in current libraries first
+2. Prefer AndroidX/Jetpack libraries when available
+3. Ensure compatibility with current Compose BOM version
+4. Add to libs.versions.toml for version management
+5. Update this documentation with links and usage notes
+
+### Version Management
+- All versions are centralized in `gradle/libs.versions.toml`
+- Use BOM (Bill of Materials) for related library groups
+- Keep major dependencies (Compose, Kotlin, Hilt) aligned
+
+### Testing Strategy
+- Unit tests: JUnit + Mockito + Robolectric
+- Integration tests: Hilt testing + Room testing
+- UI tests: Compose testing + Espresso
+- Flow testing: Turbine for StateFlow/Flow testing
diff --git a/.cursor/rules/rules.arch.mdc b/.cursor/rules/rules.arch.mdc
new file mode 100644
index 000000000..9737c9115
--- /dev/null
+++ b/.cursor/rules/rules.arch.mdc
@@ -0,0 +1,28 @@
+---
+description:
+globs:
+alwaysApply: true
+---
+### Business Layer Design Principles
+- Always prefer creating specialized methods over complex inline logic
+- If a feature implementation requires >5 lines of method chaining, extract to a new method
+- New feature implementations should read like the business requirement
+
+### API Design Guidelines
+- Create domain-specific methods that match the business language
+ - Method names should describe WHAT, not HOW
+ - Hide complexity behind simple, single-purpose APIs
+
+### Refactoring Triggers
+- When implementing new features, always ask: "Does the business layer need a new method to support this cleanly?"
+- If you see repeated patterns across different features, extract them into reusable methods
+- Prefer failing fast with meaningful defaults over complex null handling in business logic
+
+### Business Logic Method Design
+- Service methods should handle their own error cases and return simple types
+ - Let callers focus on business logic, not infrastructure concerns
+
+### Implementation Priority
+- Before writing complex feature logic, spend time designing the ideal API first
+- Ask: "What would the cleanest possible calling code look like?" then work backwards
+- Create the relevant methods needed to achieve that clean calling code
diff --git a/.cursor/rules/rules.main.mdc b/.cursor/rules/rules.main.mdc
new file mode 100644
index 000000000..fe903e7c2
--- /dev/null
+++ b/.cursor/rules/rules.main.mdc
@@ -0,0 +1,68 @@
+---
+description:
+globs:
+alwaysApply: true
+---
+## Setup rules:
+- you're a senior kotlin android developer with very good knowledge of react native and swift, and you're also an expert in bitcoin and lightning network developer, especially with ldk and ldk-node.
+- do not add code comments, except for math, complex logic, docs
+- do not remove existing code comments
+- when working with libraries, always check `.cursor/notes/libs.md` for documentation links and usage patterns
+
+## Rules for communication:
+- don't be overly enthusiastic in your words, be terse, plain and factual
+- do not reply with arguments why the changes are better
+
+---
+
+## Rules for Android code:
+- use official kotlin code guide
+- always use trailing commas in parameters list, except after modifiers parameter
+- when invoking any composable function, always pass the modifier argument last in the call, sse named parameters if needed to ensure this order, and omit trailing commas after it.
+- never modify `strings.xml`
+- always keep compose preview at end of file
+- split screen composables into stateful wrapper parent and stateless child which can be rendered in the previews.
+- wrap previews in `AppThemeSurface` composable, and name them simple, like `Preview`, `Preview2`.
+- In every ViewModel file, declare the UiState data class immediately AFTER the ViewModel class (never above it)
+- prefer to use `_uiState.update {}` for updating ui state flow value
+- prefer to add business logic to UI via viewmodels, which can delegate to repositories/service classes
+- avoid creating intermediate tuples/triples variables in UI, instead inline logic to the components properties
+- do not wire navigation through viewmodel
+- do not add logs to viewmodels
+- never add any viewmodel as dependency to another viewmodel
+- name composable callback parameters with prefix `onClick`, like `onClickSomething` not `onSomethingClick`
+- when possible, pass entire `uiState` to the inner Content composable, not individual parameters for each uiState param
+- use existing components from `to.bitkit.ui.components` package
+- prefer `Dp` unit for new composable parameters
+- prefer notation `5000u` and `5000uL` for unsigned integers and long, avoid using `5000U` and `5000UL`
+- logging should happen at repository level, viewmodel methods should not re-log if repository already logs the same thing
+- prefer list condition check using `in`, i.e. use `myElement in myList` instead of `myList.contains(myElement)`
+- for localization use `getString(resId).replace("{param}", hostParam)`
+- always trim user input strings, apply the `userInput.trim()` in the viewmodel
+- prefer `runCatching` over try/catch
+- in compose callbacks, instead of calling `stringResource()` declare `val context = LocalContext.current` and use `context.getString()`
+- prefer using `ULong` wherever possible and write ULong values as `1000u` instead of `1000UL`
+- prefer to expression body for potential one-line methods
+
+### Rules for Compose Navigation:
+- we use strongly typed navigation in compose, most routes are in `ContentView.kt` file, package `to.bitkit.ui.Routes`
+- you should use navigation like: `navController.navigate(Routes.ExternalConnection)`
+
+## Rules to map RN (react native) to Compose:
+- use `docs/screens-map.md` for mapping screens
+- map `color = "secondary"` to `color = Colors.White64`
+- map colors to `Colors.kt`
+- map `Pressable` or `TouchableOpacity` to `Modifier.clickableAlpha { onClick() } `
+- map text components to compose components from `ui/components/Text.kt`
+- map settings components to compose components from from `ui/components/settings/*.kt`
+- use spacer components from `ui/components/Spacers.kt`
+- map `EChannelStatus.open` from react native to `channelDetails.isChannelReady` in kotlin
+- map `EChannelStatus.pending` from react native to `!channelDetails.isChannelReady` in kotlin
+- map border to `HorizontalDivider()`, without color or thickness params, default color is already White10.
+
+---
+
+## Rules for Android Unit tests and Instrumentation tests:
+- run unit tests for specific files like this: `./gradlew :app:testDevDebugUnitTest --tests "to.bitkit.repositories.LightningRepoTest"`
+- write unit tests in the same style and using same libraries as: `CurrencyRepoTest`, `LightningRepoTest`, `WalletRepoTest`
+- in unit tests, use asserts from `kotlin.test` and mockito-kotlin for mocks
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
index 55dcdf70c..7cef9571e 100644
--- a/.github/workflows/claude-code-review.yml
+++ b/.github/workflows/claude-code-review.yml
@@ -51,7 +51,8 @@ jobs:
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
- # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
- claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
+ Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
index 671e05298..511e692b7 100644
--- a/.github/workflows/claude.yml
+++ b/.github/workflows/claude.yml
@@ -13,10 +13,14 @@ on:
jobs:
claude:
if: |
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
- (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') &&
+ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') &&
+ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') &&
+ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
+ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association))
runs-on: ubuntu-latest
permissions:
contents: write # Allow creating branches/commits
@@ -45,6 +49,5 @@ jobs:
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
+ # or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'
-
diff --git a/.gitignore b/.gitignore
index 43ecfebbd..6da892352 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.iml
.gradle
.idea
+!.idea/codeStyles/
.kotlin
.DS_Store
/build
@@ -10,9 +11,8 @@
local.properties
# AI
.ai
-.cursor
*.local.*
-CLAUDE.md
+!*.local.template*
# Secrets
google-services.json
.env
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..b175032ad
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..79ee123c2
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..96829e391
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,194 @@
+# CLAUDE.md
+
+This file provides guidance to AI agents like Cursor/Claude Code/Codex/WARP when working with code in this repository.
+
+## Build Commands
+
+```sh
+# compile
+./gradlew compileDevDebugKotlin
+
+# Build for dev
+./gradlew assembleDevDebug
+
+# Run unit tests
+./gradlew testDevDebugUnitTest
+
+# Run specific unit test file
+./gradlew testDevDebugUnitTest --tests LightningRepoTest
+
+# Run instrumented tests
+./gradlew connectedDevDebugAndroidTest
+
+# Build for E2E tests
+E2E=true ./gradlew assembleDevRelease
+
+# Build for E2E tests with geoblocking disabled
+GEO=false E2E=true ./gradlew assembleDevRelease
+
+# Lint using detekt
+./gradlew detekt
+
+# Auto-format using detekt
+./gradlew detekt --auto-correct
+
+# Update detekt baseline
+./gradlew detektBaseline
+
+# Install dev build
+./gradlew installDevDebug
+
+# Clean build artifacts
+./gradlew clean
+```
+
+## Architecture Overview
+
+### Tech Stack
+- **Language**: Kotlin
+- **UI Framework**: Jetpack Compose with Material3
+- **Architecture**: MVVM with Hilt dependency injection
+- **Database**: Room
+- **Networking**: Ktor
+- **Bitcoin/Lightning**: LDK Node, bitkitcore library
+- **State Management**: StateFlow, SharedFlow
+- **Navigation**: Compose Navigation with strongly typed routes
+- **Push Notifications**: Firebase
+- **Storage**: DataStore with json files
+
+### Project Structure
+- **app/src/main/java/to/bitkit/**
+ - **App.kt**: Application class with Hilt setup
+ - **ui/**: All UI components
+ - **MainActivity.kt**: Single activity hosting all screens
+ - **screens/**: Feature-specific screens organized by domain
+ - **components/**: Reusable UI components
+ - **theme/**: Material3 theme configuration
+ - **viewmodels/**: Shared ViewModels for business logic
+ - **repositories/**: Data access layer
+ - **services/**: Core services (Lightning, Currency, etc.)
+ - **data/**: Data layer: database, DTOs, and data stores
+ - **di/**: Dependency Injection: Hilt modules
+ - **models/**: Domain models
+ - **ext/**: Kotlin extensions
+ - **utils/**: Utility functions
+ - **usecases/**: Domain layer: use cases
+
+### Key Architecture Patterns
+1. **Single Activity Architecture**: MainActivity hosts all screens via Compose Navigation
+2. **Repository Pattern**: Repositories abstract data sources from ViewModels
+3. **Service Layer**: Core business logic in services (LightningService, WalletService)
+4. **Reactive State Management**: ViewModels expose UI state via StateFlow
+5. **Coroutine-based Async**: All async operations use Kotlin coroutines
+
+### Build Variants
+- **dev**: Regtest network for development
+- **tnet**: Testnet network
+- **mainnet**: Production (currently commented out)
+
+## Common Pitfalls
+
+### ❌ DON'T
+```kotlin
+GlobalScope.launch { } // Use viewModelScope
+val result = nullable!!.doSomething() // Use safe calls
+Text("Send Payment") // Use string resources
+class Service(@Inject val vm: ViewModel) // Never inject VMs
+suspend fun getData() = runBlocking { } // Use withContext
+```
+
+### ✅ DO
+```kotlin
+viewModelScope.launch { }
+val result = nullable?.doSomething() ?: default
+Text(stringResource(R.string.send_payment))
+class Service { fun process(data: Data) }
+suspend fun getData() = withContext(Dispatchers.IO) { }
+```
+
+## Key File Paths
+
+- **Main Activity**: `app/src/main/java/to/bitkit/ui/MainActivity.kt`
+- **Navigation**: `app/src/main/java/to/bitkit/ui/ContentView.kt`
+- **Lightning Service**: `app/src/main/java/to/bitkit/services/LightningService.kt`
+- **App ViewModel**: `app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt`
+- **Wallet ViewModel**: `app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt`
+
+## Common Patterns
+
+### ViewModel State
+```kotlin
+private val _uiState = MutableStateFlow(InitialState)
+val uiState: StateFlow = _uiState.asStateFlow()
+
+fun updateState(action: Action) {
+ viewModelScope.launch {
+ _uiState.update { it.copy(/* fields */) }
+ }
+}
+```
+
+### Repository
+```kotlin
+suspend fun getData(): Result = withContext(Dispatchers.IO) {
+ runCatching {
+ Result.success(apiService.fetchData())
+ }.onFailure { e ->
+ Logger.error("Failed", e = e, context = TAG)
+ }
+}
+```
+
+### Rules
+- USE coding rules from `.cursor/default.rules.mdc`
+- ALWAYS run `./gradlew compileDevDebugKotlin` after code changes to verify code compiles
+- ALWAYS run `./gradlew testDevDebugUnitTest` after code changes to verify tests succeed and fix accordingly
+- ALWAYS run `./gradlew detekt` after code changes to check for new lint issues and fix accordingly
+- ALWAYS ask clarifying questions to ensure an optimal plan when encountering functional or technical uncertainties in requests
+- ALWAYS when fixing lint or test failures prefer to do the minimal amount of changes to fix the issues
+- USE single-line commit messages under 50 chars; use template format: `feat: add something new`
+- USE `git diff HEAD sourceFilePath` to diff an uncommitted file against the last commit
+- ALWAYS check existing code patterns before implementing new features
+- USE existing extensions and utilities rather than creating new ones
+- ALWAYS consider applying YAGNI (You Aren't Gonna Need It) principle for new code
+- ALWAYS reuse existing constants
+- ALWAYS ensure a method exist before calling it
+- ALWAYS remove unused code after refactors
+- ALWAYS follow Material3 design guidelines for UI components
+- ALWAYS ensure proper error handling in coroutines
+- ALWAYS acknowledge datastore async operations run synchronously in a suspend context
+- NEVER use `runBlocking` in suspend functions
+- ALWAYS pass the TAG as context to `Logger` calls, e.g. `Logger.debug("message", context = TAG)`
+- ALWAYS use the Result API instead of try-catch
+- NEVER wrap methods returning `Result` in try-catch
+- NEVER inject ViewModels as dependencies - Only android activities and composable functions can use viewmodels
+- NEVER hardcode strings and always preserve string resources
+- ALWAYS localize in ViewModels using injected `@ApplicationContext`, e.g. `context.getString()`
+- ALWAYS use `remember` for expensive Compose computations
+- ALWAYS add modifiers to the last place in the argument list when calling `@Composable` functions
+- PREFER declaring small dependant classes, constants, interfaces or top-level functions in the same file with the core class where these are used
+- ALWAYS create data classes for state AFTER viewModel class in same file
+- ALWAYS return early where applicable, PREFER guard-like `if` conditions like `if (condition) return`
+- ALWAYS write the documentation for new features as markdown files in `docs/`
+- NEVER write code in the documentation files
+- NEVER add code comments to private functions, classes, etc
+- ALWAYS use `_uiState.update { }`, NEVER use `_stateFlow.value =`
+- ALWAYS add the warranted changes in unit tests to keep the unit tests succeeding
+- ALWAYS follow the patterns of the existing code in `app/src/test` when writing new unit tests
+- ALWAYS be mindful of thread safety when working with mutable lists & state
+- ALWAYS split screen composables into parent accepting viewmodel + inner private child accepting state and callbacks `Content()`
+- ALWAYS name lambda parameters in a composable function using present tense, NEVER use past tense
+- ALWAYS list 3 suggested commit messages after implementation work
+- NEVER use `wheneverBlocking` when in an unit test where you're using expression body and already wrapping the test with a `= test {}` lambda.
+- ALWAYS add business logic to Repository layer via methods returning `Result` and use it in ViewModels
+- ALWAYS use services to wrap RUST code exposed via bindings
+- ALWAYS order upstream architectural data flow this way: `UI -> ViewModel -> Repository -> RUST` and vice-versa for downstream
+- ALWAYS add new string string resources in alphabetical order in `strings.xml`
+- ALWAYS use template in `.github/pull_request_template.md` for PR descriptions
+- ALWAYS wrap `ULong` numbers with `USat` in arithmetic operations, to guard against overflows
+
+### Architecture Guidelines
+- Use `LightningNodeService` to manage background notifications while the node is running
+- Use `LightningService` to wrap node's RUST APIs and manage the inner lifecycle of the node
+- Use `LightningRepo` to defining the business logic for the node operations, usually delegating to `LightningService`
+- Use `WakeNodeWorker` to manage the handling of remote notifications received via cloud messages
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 000000000..47dc3e3d8
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/GEMINI.md b/GEMINI.md
new file mode 120000
index 000000000..47dc3e3d8
--- /dev/null
+++ b/GEMINI.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/README.md b/README.md
index 926fdd967..f8e3ad2a0 100644
--- a/README.md
+++ b/README.md
@@ -172,6 +172,17 @@ Please focus on:
- Thread safety in coroutines
```
+#### Local Development Setup (YOLO Mode)
+
+To enable auto-approved permissions for Claude Code during local development:
+
+```sh
+cp .claude/settings.local.template.json .claude/settings.local.json
+```
+
+This reduces confirmation prompts for common operations (Bash, Read, Edit, Write, etc.).
+Destructive operations like `rm -rf`, `git commit`, and `git push` still require confirmation.
+
## License
This project is licensed under the MIT License.
diff --git a/WARP.md b/WARP.md
new file mode 120000
index 000000000..47dc3e3d8
--- /dev/null
+++ b/WARP.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1ef22e2c9..4c0960f94 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,7 +2,7 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
-import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties
@@ -40,11 +40,11 @@ val locales = listOf("en", "ar", "ca", "cs", "de", "el", "es", "fr", "it", "nl",
android {
namespace = "to.bitkit"
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
applicationId = "to.bitkit"
minSdk = 28
- targetSdk = 35
+ targetSdk = 36
versionCode = 15
versionName = "0.0.15"
testInstrumentationRunner = "to.bitkit.test.HiltTestRunner"
@@ -127,8 +127,15 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
- kotlinOptions {
- jvmTarget = "11"
+ kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_11
+ freeCompilerArgs.addAll(
+ listOf(
+ "-XXLanguage:+PropertyParamAnnotationDefaultTargetMode",
+ )
+ )
+ }
}
buildFeatures {
buildConfig = true
@@ -168,8 +175,6 @@ android {
composeCompiler {
featureFlags = setOf(
- ComposeFeatureFlag.StrongSkipping.disabled(),
- ComposeFeatureFlag.OptimizeNonSkippingGroups,
)
reportsDestination = layout.buildDirectory.dir("compose_compiler")
}
@@ -196,7 +201,7 @@ dependencies {
implementation(libs.bouncycastle.provider.jdk)
implementation(libs.ldk.node.android) { exclude(group = "net.java.dev.jna", module = "jna") }
implementation(libs.bitkit.core)
- implementation(libs.vss)
+ implementation(libs.vss.client)
// Firebase
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.messaging)
diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index 56314ea81..b2beb5587 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -6,78 +6,14 @@
ComplexCondition:ElectrumConfigViewModel.kt$ElectrumConfigViewModel$currentState.host.isBlank() || port == null || port <= 0 || protocol == null
ComplexCondition:MapWebViewClient.kt$MapWebViewClient$it.errorCode == ERROR_HOST_LOOKUP || it.errorCode == ERROR_CONNECT || it.errorCode == ERROR_TIMEOUT || it.errorCode == ERROR_FILE_NOT_FOUND
ComplexCondition:ShopWebViewClient.kt$ShopWebViewClient$it.errorCode == ERROR_HOST_LOOKUP || it.errorCode == ERROR_CONNECT || it.errorCode == ERROR_TIMEOUT || it.errorCode == ERROR_FILE_NOT_FOUND
- ComposableParamOrder:ActivityDetailScreen.kt$ActivityDetailScreen
- ComposableParamOrder:ActivityExploreScreen.kt$ActivityExploreScreen
- ComposableParamOrder:ActivityIcon.kt$ActivityIcon
- ComposableParamOrder:ActivityIcon.kt$CircularIcon
- ComposableParamOrder:AppStatus.kt$AppStatus
- ComposableParamOrder:AuthCheckView.kt$AuthCheckView
- ComposableParamOrder:AuthCheckView.kt$AuthCheckViewContent
- ComposableParamOrder:AuthCheckView.kt$PinPad
- ComposableParamOrder:BalanceHeaderView.kt$BalanceHeader
- ComposableParamOrder:BlockCard.kt$BlockCard
- ComposableParamOrder:BoostTransactionSheet.kt$BoostTransactionSheet
- ComposableParamOrder:BoostTransactionSheet.kt$QuantityButton
- ComposableParamOrder:CalculatorCard.kt$CalculatorCard
- ComposableParamOrder:CalculatorCard.kt$CalculatorCardContent
- ComposableParamOrder:CalculatorPreviewScreen.kt$CalculatorPreviewScreen
- ComposableParamOrder:FactsCard.kt$FactsCard
- ComposableParamOrder:HeadlineCard.kt$HeadlineCard
- ComposableParamOrder:HomeScreen.kt$Content
- ComposableParamOrder:InfoScreenContent.kt$InfoScreenContent
- ComposableParamOrder:Money.kt$MoneyCaptionB
- ComposableParamOrder:OnboardingSlidesScreen.kt$OnboardingSlidesScreen
- ComposableParamOrder:PriceCard.kt$PriceCard
- ComposableParamOrder:ReceiveConfirmScreen.kt$ReceiveConfirmScreen
- ComposableParamOrder:ReportIssueScreen.kt$ReportIssueScreen
- ComposableParamOrder:RestoreWalletScreen.kt$MnemonicInputField
- ComposableParamOrder:SheetHost.kt$SheetHost
- ComposableParamOrder:SpendingAmountScreen.kt$SpendingAmountScreen
- ComposableParamOrder:SuggestionCard.kt$SuggestionCard
- ComposableParamOrder:SwipeToConfirm.kt$SwipeToConfirm
- ComposableParamOrder:TextInput.kt$TextInput
- ComposableParamOrder:WeatherCard.kt$WeatherCard
- ComposableParamOrder:WidgetsSettingsScreen.kt$WidgetsSettingsContent
- CompositionLocalAllowlist:Locals.kt$LocalActivityListViewModel
- CompositionLocalAllowlist:Locals.kt$LocalAppViewModel
- CompositionLocalAllowlist:Locals.kt$LocalBackupsViewModel
- CompositionLocalAllowlist:Locals.kt$LocalBalances
- CompositionLocalAllowlist:Locals.kt$LocalBlocktankViewModel
- CompositionLocalAllowlist:Locals.kt$LocalCurrencies
- CompositionLocalAllowlist:Locals.kt$LocalCurrencyViewModel
- CompositionLocalAllowlist:Locals.kt$LocalSettingsViewModel
- CompositionLocalAllowlist:Locals.kt$LocalTransferViewModel
- CompositionLocalAllowlist:Locals.kt$LocalWalletViewModel
- ConstructorParameterNaming:AddressChecker.kt$AddressInfo$val chain_stats: AddressStats
- ConstructorParameterNaming:AddressChecker.kt$AddressInfo$val mempool_stats: AddressStats
- ConstructorParameterNaming:AddressChecker.kt$AddressStats$val funded_txo_count: Int
- ConstructorParameterNaming:AddressChecker.kt$AddressStats$val funded_txo_sum: Int
- ConstructorParameterNaming:AddressChecker.kt$AddressStats$val spent_txo_count: Int
- ConstructorParameterNaming:AddressChecker.kt$AddressStats$val spent_txo_sum: Int
- ConstructorParameterNaming:AddressChecker.kt$AddressStats$val tx_count: Int
- ConstructorParameterNaming:AddressChecker.kt$TxInput$val is_coinbase: Boolean? = null
- ConstructorParameterNaming:AddressChecker.kt$TxInput$val scriptsig_asm: String? = null
- ConstructorParameterNaming:AddressChecker.kt$TxOutput$val scriptpubkey_address: String? = null
- ConstructorParameterNaming:AddressChecker.kt$TxOutput$val scriptpubkey_asm: String? = null
- ConstructorParameterNaming:AddressChecker.kt$TxOutput$val scriptpubkey_type: String? = null
- ConstructorParameterNaming:AddressChecker.kt$TxStatus$val block_hash: String? = null
- ConstructorParameterNaming:AddressChecker.kt$TxStatus$val block_height: Int? = null
- ConstructorParameterNaming:AddressChecker.kt$TxStatus$val block_time: Long? = null
- CyclomaticComplexMethod:ActivityDetailScreen.kt$@Composable private fun ActivityDetailContent( item: Activity, tags: List<String>, onRemoveTag: (String) -> Unit, onAddTagClick: () -> Unit, onClickBoost: () -> Unit, onExploreClick: (String) -> Unit, onCopy: (String) -> Unit, )
CyclomaticComplexMethod:ActivityListGrouped.kt$private fun groupActivityItems(activityItems: List<Activity>): List<Any>
CyclomaticComplexMethod:ActivityRow.kt$@Composable fun ActivityRow( item: Activity, onClick: (String) -> Unit, testTag: String, )
- CyclomaticComplexMethod:ActivityRow.kt$@Composable private fun TransactionStatusText( txType: PaymentType, isLightning: Boolean, status: PaymentState?, isTransfer: Boolean, )
- CyclomaticComplexMethod:AppStatusScreen.kt$@Composable private fun Content( uiState: AppStatusUiState = AppStatusUiState(), onBack: () -> Unit = {}, onClose: () -> Unit = {}, onInternetClick: () -> Unit = {}, onElectrumClick: () -> Unit = {}, onNodeClick: () -> Unit = {}, onChannelsClick: () -> Unit = {}, onBackupClick: () -> Unit = {}, )
CyclomaticComplexMethod:AppViewModel.kt$AppViewModel$private fun observeSendEvents()
CyclomaticComplexMethod:AppViewModel.kt$AppViewModel$private suspend fun handleSanityChecks(amountSats: ULong)
CyclomaticComplexMethod:BlocktankRegtestScreen.kt$@Composable fun BlocktankRegtestScreen( navController: NavController, viewModel: BlocktankRegtestViewModel = hiltViewModel(), )
- CyclomaticComplexMethod:ChannelDetailScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun Content( channel: ChannelUi, blocktankOrders: List<IBtOrder> = emptyList(), cjitEntries: List<IcJitEntry> = emptyList(), txDetails: TxDetails? = null, isRefreshing: Boolean = false, onBack: () -> Unit = {}, onClose: () -> Unit = {}, onRefresh: () -> Unit = {}, onCopyText: (String) -> Unit = {}, onOpenUrl: (String) -> Unit = {}, onSupport: (Any) -> Unit = {}, onCloseConnection: () -> Unit = {}, )
CyclomaticComplexMethod:ConfirmMnemonicScreen.kt$@Composable fun ConfirmMnemonicScreen( uiState: BackupContract.UiState, onContinue: () -> Unit, onBack: () -> Unit, )
- CyclomaticComplexMethod:ContentView.kt$@Composable fun ContentView( appViewModel: AppViewModel, walletViewModel: WalletViewModel, blocktankViewModel: BlocktankViewModel, currencyViewModel: CurrencyViewModel, activityListViewModel: ActivityListViewModel, transferViewModel: TransferViewModel, settingsViewModel: SettingsViewModel, backupsViewModel: BackupsViewModel, )
- CyclomaticComplexMethod:CoreService.kt$ActivityService$private suspend fun processOnchainPayment( kind: PaymentKind.Onchain, payment: PaymentDetails, forceUpdate: Boolean, )
CyclomaticComplexMethod:HealthRepo.kt$HealthRepo$private fun collectState()
CyclomaticComplexMethod:HomeScreen.kt$@Composable fun HomeScreen( mainUiState: MainUiState, drawerState: DrawerState, rootNavController: NavController, walletNavController: NavHostController, settingsViewModel: SettingsViewModel, walletViewModel: WalletViewModel, appViewModel: AppViewModel, activityListViewModel: ActivityListViewModel, homeViewModel: HomeViewModel = hiltViewModel(), )
- CyclomaticComplexMethod:LightningService.kt$LightningService$private fun logEvent(event: Event)
CyclomaticComplexMethod:SendSheet.kt$@Composable fun SendSheet( appViewModel: AppViewModel, walletViewModel: WalletViewModel, startDestination: SendRoute = SendRoute.Recipient, )
CyclomaticComplexMethod:SettingsButtonRow.kt$@Composable fun SettingsButtonRow( title: String, modifier: Modifier = Modifier, subtitle: String? = null, value: SettingsButtonValue = SettingsButtonValue.None, description: String? = null, iconRes: Int? = null, iconTint: Color = Color.Unspecified, iconSize: Dp = 32.dp, maxLinesSubtitle: Int = Int.MAX_VALUE, enabled: Boolean = true, loading: Boolean = false, onClick: () -> Unit, )
CyclomaticComplexMethod:Slider.kt$@Composable fun StepSlider( value: Int, steps: List<Int>, onValueChange: (Int) -> Unit, modifier: Modifier = Modifier, )
@@ -99,93 +35,44 @@
ForbiddenComment:ActivityDetailScreen.kt$/* TODO: Implement assign functionality */
ForbiddenComment:ActivityRow.kt$// TODO: calculate confirmsIn text
ForbiddenComment:BoostTransactionViewModel.kt$BoostTransactionUiState$// TODO: Implement dynamic time estimation
- ForbiddenComment:ChannelStatusView.kt$// TODO: handle closed channels marking & detection
ForbiddenComment:ContentView.kt$// TODO: display as sheet
ForbiddenComment:Env.kt$Env$// TODO: remove this to load from BT API instead
ForbiddenComment:ExternalNodeViewModel.kt$ExternalNodeViewModel$// TODO: pass customFeeRate to ldk-node when supported
ForbiddenComment:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$// TODO: sort channels to get consistent index; node.listChannels returns a list in random order
- ForbiddenComment:LightningNodeService.kt$LightningNodeService$// TODO: Get from resources
+ ForbiddenComment:LightningService.kt$LightningService$// TODO: cleanup sensitive data after implementing a `SecureString` value holder for Keychain return values
ForbiddenComment:Notifications.kt$// TODO: review if needed:
ForbiddenComment:SuccessScreen.kt$// TODO: verify backup
FunctionOnlyReturningConstant:ShopWebViewInterface.kt$ShopWebViewInterface$@JavascriptInterface fun isReady(): Boolean
ImplicitDefaultLocale:BlocksService.kt$BlocksService$String.format("%.2f", blockInfo.difficulty / 1_000_000_000_000.0)
ImplicitDefaultLocale:PriceService.kt$PriceService$String.format("%.2f", price)
InstanceOfCheckForException:LightningService.kt$LightningService$e is NodeException
- LambdaParameterEventTrailing:CalculatorCard.kt$onFiatChange
- LambdaParameterEventTrailing:QrScanningScreen.kt$onSubmitDebug
- LambdaParameterEventTrailing:ReceiveQrScreen.kt$onClickEditInvoice
- LambdaParameterEventTrailing:SettingsButtonRow.kt$onClick
- LambdaParameterEventTrailing:SuggestionCard.kt$onClick
- LambdaParameterEventTrailing:ToastView.kt$onDismiss
- LambdaParameterInRestartableEffect:AuthCheckView.kt$validatePin
- LambdaParameterInRestartableEffect:BiometricPrompt.kt$onSuccess
- LambdaParameterInRestartableEffect:BoostTransactionSheet.kt$onFailure
- LambdaParameterInRestartableEffect:BoostTransactionSheet.kt$onMaxFee
- LambdaParameterInRestartableEffect:BoostTransactionSheet.kt$onMinFee
- LambdaParameterInRestartableEffect:BoostTransactionSheet.kt$onSuccess
- LambdaParameterInRestartableEffect:EditInvoiceScreen.kt$navigateReceiveConfirm
- LambdaParameterInRestartableEffect:EditInvoiceScreen.kt$onBack
- LambdaParameterInRestartableEffect:EditInvoiceScreen.kt$updateInvoice
- LambdaParameterInRestartableEffect:InitializingWalletView.kt$onComplete
- LambdaParameterInRestartableEffect:LnurlChannelScreen.kt$onConnected
- LambdaParameterInRestartableEffect:PinConfirmScreen.kt$onPinConfirmed
- LambdaParameterInRestartableEffect:PricePreviewScreen.kt$onClose
- LambdaParameterInRestartableEffect:QrCodeImage.kt$onBitmapGenerated
- LambdaParameterInRestartableEffect:QrScanningScreen.kt$onBack
- LambdaParameterInRestartableEffect:QrScanningScreen.kt$onScanSuccess
- LambdaParameterInRestartableEffect:ReportIssueScreen.kt$navigateResultScreen
- LambdaParameterInRestartableEffect:SendCoinSelectionScreen.kt$onRender
- LambdaParameterInRestartableEffect:SendConfirmScreen.kt$onNavigateToPin
- LambdaParameterInRestartableEffect:SendPinCheckScreen.kt$onSuccess
- LambdaParameterInRestartableEffect:SendQuickPayScreen.kt$onPaymentComplete
- LambdaParameterInRestartableEffect:SendQuickPayScreen.kt$onShowError
- LambdaParameterInRestartableEffect:SheetHost.kt$onDismiss
- LambdaParameterInRestartableEffect:SpendingAmountScreen.kt$onOrderCreated
- LambdaParameterInRestartableEffect:SpendingAmountScreen.kt$toast
- LambdaParameterInRestartableEffect:SpendingAmountScreen.kt$toastException
LargeClass:AppViewModel.kt$AppViewModel : ViewModel
LargeClass:LightningRepo.kt$LightningRepo
- LongMethod:AppViewModel.kt$AppViewModel$private fun observeLdkNodeEvents()
LongMethod:AppViewModel.kt$AppViewModel$private suspend fun proceedWithPayment()
+ LongMethod:ContentView.kt$@Suppress("LongParameterList") private fun NavGraphBuilder.home( walletViewModel: WalletViewModel, appViewModel: AppViewModel, activityListViewModel: ActivityListViewModel, settingsViewModel: SettingsViewModel, navController: NavHostController, drawerState: DrawerState, )
LongMethod:ContentView.kt$private fun NavGraphBuilder.widgets( navController: NavHostController, settingsViewModel: SettingsViewModel, currencyViewModel: CurrencyViewModel, )
LongMethod:CoreService.kt$ActivityService$suspend fun generateRandomTestData(count: Int = 100)
- LongMethod:LightningService.kt$LightningService$private fun logEvent(event: Event)
LongMethod:MainActivity.kt$MainActivity$override fun onCreate(savedInstanceState: Bundle?)
LongParameterList:BiometricPrompt.kt$( activity: Context, title: String, cancelButtonText: String, onAuthSucceed: () -> Unit, onAuthFailed: (() -> Unit), onAuthError: ((errorCode: Int, errString: CharSequence) -> Unit), )
LongParameterList:BiometricPrompt.kt$( activity: Context, title: String, cancelButtonText: String, onAuthSucceeded: () -> Unit, onAuthFailed: (() -> Unit), onAuthError: ((errorCode: Int, errString: CharSequence) -> Unit), onUnsupported: () -> Unit, )
LongParameterList:CoreService.kt$ActivityService$( filter: ActivityFilter? = null, txType: PaymentType? = null, tags: List<String>? = null, search: String? = null, minDate: ULong? = null, maxDate: ULong? = null, limit: UInt? = null, sortDirection: SortDirection? = null, )
LongParameterList:CoreService.kt$BlocktankService$( channelSizeSat: ULong, invoiceSat: ULong, invoiceDescription: String, nodeId: String, channelExpiryWeeks: UInt, options: CreateCjitOptions, )
LongParameterList:CoreService.kt$OnchainService$( mnemonicPhrase: String, derivationPathStr: String?, network: Network?, bip39Passphrase: String?, isChange: Boolean?, startIndex: UInt?, count: UInt?, )
- LongParameterList:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$( @ApplicationContext private val context: Context, @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val lightningRepo: LightningRepo, internal val blocktankRepo: BlocktankRepo, private val logsRepo: LogsRepo, private val addressChecker: AddressChecker, private val ldkNodeEventBus: LdkNodeEventBus, private val walletRepo: WalletRepo, )
- LongParameterList:LightningRepo.kt$LightningRepo$( @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val lightningService: LightningService, private val ldkNodeEventBus: LdkNodeEventBus, private val settingsStore: SettingsStore, private val coreService: CoreService, private val lspNotificationsService: LspNotificationsService, private val firebaseMessaging: FirebaseMessaging, private val keychain: Keychain, private val lnurlService: LnurlService, private val cacheStore: CacheStore, )
- LongParameterList:Notifications.kt$( title: String?, text: String?, extras: Bundle? = null, bigText: String? = null, id: Int = Random.nextInt(), context: Context, )
LongParameterList:WidgetsRepo.kt$WidgetsRepo$( @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val newsService: NewsService, private val factsService: FactsService, private val blocksService: BlocksService, private val weatherService: WeatherService, private val priceService: PriceService, private val widgetsStore: WidgetsStore, private val settingsStore: SettingsStore, )
LoopWithTooManyJumpStatements:MonetaryVisualTransformation.kt$MonetaryVisualTransformation.<no name provided>$for
- LoopWithTooManyJumpStatements:TransferViewModel.kt$TransferViewModel$while
MagicNumber:ActivityDetailScreen.kt$40
MagicNumber:ActivityExploreScreen.kt$40
MagicNumber:ActivityListViewModel.kt$ActivityListViewModel$300
MagicNumber:AddressViewerScreen.kt$1500000L
MagicNumber:AddressViewerScreen.kt$250000L
- MagicNumber:AddressViewerScreen.kt$50000L
MagicNumber:AddressViewerViewModel.kt$AddressViewerViewModel$300
MagicNumber:AppStatus.kt$0.4f
- MagicNumber:AppViewModel.kt$AppViewModel$1000
- MagicNumber:AppViewModel.kt$AppViewModel$250
- MagicNumber:AppViewModel.kt$AppViewModel$500
MagicNumber:ArticleModel.kt$24
MagicNumber:ArticleModel.kt$30
MagicNumber:ArticleModel.kt$60
- MagicNumber:AutoReadClipboardHandler.kt$1000
MagicNumber:BackupNavSheetViewModel.kt$BackupNavSheetViewModel$200
- MagicNumber:BackupsViewModel.kt$BackupsViewModel$500
- MagicNumber:BiometricsView.kt$5
- MagicNumber:ChangePinConfirmScreen.kt$500
MagicNumber:ChannelDetailScreen.kt$1.5f
- MagicNumber:ChannelOrdersScreen.kt$10
MagicNumber:ConfirmMnemonicScreen.kt$300
- MagicNumber:ContentView.kt$100
- MagicNumber:ContentView.kt$500
MagicNumber:CoreService.kt$ActivityService$64
MagicNumber:Crypto.kt$Crypto$16
MagicNumber:Crypto.kt$Crypto$32
@@ -195,60 +82,33 @@
MagicNumber:ElectrumServer.kt$60001
MagicNumber:ElectrumServer.kt$60002
MagicNumber:HomeScreen.kt$0.8f
- MagicNumber:HomeScreen.kt$3
MagicNumber:HttpModule.kt$HttpModule$30_000
MagicNumber:HttpModule.kt$HttpModule$60_000
- MagicNumber:InitializingWalletView.kt$500
MagicNumber:InitializingWalletView.kt$99.9
- MagicNumber:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$500
- MagicNumber:NewsService.kt$NewsService$10
- MagicNumber:OnboardingSlidesScreen.kt$3
- MagicNumber:OnboardingSlidesScreen.kt$4
- MagicNumber:OnboardingSlidesScreen.kt$5
- MagicNumber:PinConfirmScreen.kt$500
MagicNumber:PinPromptScreen.kt$0.8f
- MagicNumber:PriceCard.kt$1000
- MagicNumber:PriceCard.kt$3.0
- MagicNumber:PriceCard.kt$4.0
- MagicNumber:PricePreviewScreen.kt$3.0
- MagicNumber:PricePreviewScreen.kt$4.0
MagicNumber:ProgressSteps.kt$12f
- MagicNumber:QrScanningScreen.kt$100
- MagicNumber:ReceiveQrScreen.kt$17.33f
MagicNumber:ReceiveQrScreen.kt$32
MagicNumber:RestoreWalletScreen.kt$12
MagicNumber:SavingsConfirmScreen.kt$300
- MagicNumber:SavingsProgressScreen.kt$2500
- MagicNumber:SavingsProgressScreen.kt$5000
MagicNumber:SendConfirmScreen.kt$1_234
MagicNumber:SendConfirmScreen.kt$300
MagicNumber:SendConfirmScreen.kt$43
MagicNumber:SendConfirmScreen.kt$654_321
- MagicNumber:SettingUpScreen.kt$5000
- MagicNumber:SettingsViewModel.kt$SettingsViewModel$5000
- MagicNumber:ShareSheet.kt$100
MagicNumber:ShowMnemonicScreen.kt$12
MagicNumber:ShowMnemonicScreen.kt$24
MagicNumber:ShowMnemonicScreen.kt$300
- MagicNumber:Slider.kt$10
MagicNumber:Slider.kt$20
- MagicNumber:Slider.kt$5
- MagicNumber:Slider.kt$50
MagicNumber:SpendingConfirmScreen.kt$300
- MagicNumber:String.kt$3
MagicNumber:SwipeToConfirm.kt$1500
- MagicNumber:SwipeToConfirm.kt$500
MatchingDeclarationName:AddressType.kt$AddressTypeInfo
MatchingDeclarationName:Button.kt$ButtonSize
MatchingDeclarationName:CoinSelectPreferenceScreen.kt$CoinSelectPreferenceTestTags
MatchingDeclarationName:LightningChannel.kt$ChannelStatusUi
- MatchingDeclarationName:LightningConnectionsScreen.kt$LightningConnectionsTestTags
MatchingDeclarationName:ReceiveConfirmScreen.kt$CjitEntryDetails
MatchingDeclarationName:ReportIssueScreen.kt$ReportIssueTestTags
MatchingDeclarationName:ResetAndRestoreScreen.kt$ResetAndRestoreTestTags
MatchingDeclarationName:SavingsProgressScreen.kt$SavingsProgressState
MatchingDeclarationName:SettingsButtonRow.kt$SettingsButtonValue
- MaxLineLength:ActivityDetailScreen.kt$description = "Unable to increase the fee any further. Otherwise, it will exceed half the current input balance"
MaxLineLength:BlocksEditScreen.kt$enabled = blocksPreferences.run { showBlock || showTime || showDate || showTransactions || showSize || showSource }
MaxLineLength:BlocktankRegtestScreen.kt$"Initiating channel close with fundingTxId: $fundingTxId, vout: $vout, forceCloseAfter: $forceCloseAfter"
MaxLineLength:BlocktankRepo.kt$BlocktankRepo$"Buying channel with lspBalanceSat: $receivingBalanceSats, channelExpiryWeeks: $channelExpiryWeeks, options: $options"
@@ -262,10 +122,6 @@
MaxLineLength:LightningRepo.kt$LightningRepo$"accelerateByCpfp error originalTxId: $originalTxId, satsPerVByte: $satsPerVByte destinationAddress: $destinationAddress"
MaxLineLength:LightningRepo.kt$LightningRepo$"accelerateByCpfp success, newDestinationTxId: $newDestinationTxId originalTxId: $originalTxId, satsPerVByte: $satsPerVByte destinationAddress: $destinationAddress"
MaxLineLength:LightningRepo.kt$LightningRepo$"bumpFeeByRbf success, replacementTxId: $replacementTxId originalTxId: $originalTxId, satsPerVByte: $satsPerVByte"
- MaxLineLength:LightningService.kt$LightningService$"⏳ Channel pending: channelId: $channelId userChannelId: $userChannelId formerTemporaryChannelId: $formerTemporaryChannelId counterpartyNodeId: $counterpartyNodeId fundingTxo: $fundingTxo"
- MaxLineLength:LightningService.kt$LightningService$"⛔ Channel closed: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId reason: $reason"
- MaxLineLength:LightningService.kt$LightningService$"👐 Channel ready: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId"
- MaxLineLength:LightningService.kt$LightningService$"🫰 Payment claimable: paymentId: $paymentId paymentHash: $paymentHash claimableAmountMsat: $claimableAmountMsat"
MaxLineLength:ReceiveLiquidityScreen.kt$if (isAdditional) R.string.wallet__receive_liquidity__label_additional else R.string.wallet__receive_liquidity__label
MaxLineLength:ReceiveLiquidityScreen.kt$if (isAdditional) R.string.wallet__receive_liquidity__nav_title_additional else R.string.wallet__receive_liquidity__nav_title
MaxLineLength:ReceiveLiquidityScreen.kt$if (isAdditional) R.string.wallet__receive_liquidity__text_additional else R.string.wallet__receive_liquidity__text
@@ -273,7 +129,6 @@
MaxLineLength:SendAddressScreen.kt$addressInput = "bitcoin:bc17tq4mtkq86vte7a26e0za560kgflwqsvxznmer5?lightning=LNBC1PQUVNP8KHGPLNF6REGS3VY5F40AJFUN4S2JUDQQNP4TK9MP6LWWLWTC3XX3UUEVYZ4EVQU3X4NQDX348QPP5WJC9DWNTAFN7FZEZFVDC3MHV67SX2LD2MG602E3LEZDMFT29JLWQSP54QKM4G8A2KD5RGEKACA3CH4XV4M2MQDN62F8S2CCRES9QYYSGQCQPCXQRRSSRZJQWQKZS03MNNHSTKR9DN2XQRC8VW5X6CEWAL8C6RW6QQ3T02T3R"
MaxLineLength:SettingsScreen.kt$if (newValue) R.string.settings__dev_enabled_message else R.string.settings__dev_disabled_message
MaxLineLength:WeatherService.kt$WeatherService$val avgFeeUsd = currencyRepo.convertSatsToFiat(avgFeeSats.toLong(), currency = USD_CURRENCY).getOrNull() ?: return FeeCondition.AVERAGE
- MaximumLineLength:ActivityDetailScreen.kt$
MaximumLineLength:BlocksEditScreen.kt$
MaximumLineLength:BlocktankRegtestScreen.kt$
MaximumLineLength:BlocktankRepo.kt$BlocktankRepo$
@@ -283,7 +138,6 @@
MaximumLineLength:LightningBalance.kt$
MaximumLineLength:LightningConnectionsScreen.kt$
MaximumLineLength:LightningRepo.kt$LightningRepo$
- MaximumLineLength:LightningService.kt$LightningService$
MaximumLineLength:ReceiveLiquidityScreen.kt$
MaximumLineLength:SecuritySettingsScreen.kt$
MaximumLineLength:SendAddressScreen.kt$
@@ -293,130 +147,19 @@
MayBeConst:Env.kt$Env.TransactionDefaults$/** * Minimum value in sats for an output. Outputs below the dust limit may not be processed because the fees * required to include them in a block would be greater than the value of the transaction itself. * */ val dustLimit = 546u
MayBeConst:Env.kt$Env.TransactionDefaults$/** Total recommended tx base fee in sats */ val recommendedBaseFee = 256u
MemberNameEqualsClassName:Keychain.kt$Keychain$private val keychain = context.keychainDataStore
- ModifierComposed:AllActivityScreen.kt$swipeToChangeTab
- ModifierComposed:Modifiers.kt$clickableAlpha
- ModifierComposed:SheetHeight.kt$sheetHeight
- ModifierMissing:AboutScreen.kt$AboutScreen
- ModifierMissing:ActivityAddTagSheet.kt$ActivityAddTagSheet
- ModifierMissing:ActivityDetailScreen.kt$ActivityDetailScreen
- ModifierMissing:ActivityListSimple.kt$ActivityListSimple
- ModifierMissing:ActivityRow.kt$ActivityRow
- ModifierMissing:AddWidgetsScreen.kt$AddWidgetsScreen
- ModifierMissing:BackupSheet.kt$BackupSheet
- ModifierMissing:BiometricsView.kt$BiometricsView
- ModifierMissing:BlocksEditScreen.kt$BlocksEditContent
- ModifierMissing:BlocksPreviewScreen.kt$BlocksPreviewContent
- ModifierMissing:BlocktankRegtestScreen.kt$BlocktankRegtestScreen
- ModifierMissing:BuyIntroScreen.kt$BuyIntroContent
- ModifierMissing:CalculatorPreviewScreen.kt$CalculatorPreviewContent
- ModifierMissing:CameraPermissionView.kt$DeniedContent
- ModifierMissing:ChannelStatusView.kt$ChannelStatusView
- ModifierMissing:CreateProfileScreen.kt$CreateProfileScreen
- ModifierMissing:CreateWalletWithPassphraseScreen.kt$CreateWalletWithPassphraseScreen
- ModifierMissing:DefaultUnitSettingsScreen.kt$DefaultUnitSettingsScreenContent
- ModifierMissing:DevSettingsScreen.kt$DevSettingsScreen
- ModifierMissing:FactsEditScreen.kt$FactsEditContent
- ModifierMissing:FactsPreviewScreen.kt$FactsPreviewContent
- ModifierMissing:FundingAdvancedScreen.kt$FundingAdvancedScreen
- ModifierMissing:FundingScreen.kt$FundingScreen
- ModifierMissing:HeadlinesEditScreen.kt$HeadlinesEditContent
- ModifierMissing:HeadlinesPreviewScreen.kt$HeadlinesPreviewContent
- ModifierMissing:HomeNav.kt$HomeNav
- ModifierMissing:InfoScreenContent.kt$InfoScreenContent
- ModifierMissing:InitializingWalletView.kt$InitializingWalletView
- ModifierMissing:IntroScreen.kt$IntroScreen
- ModifierMissing:LocalCurrencySettingsScreen.kt$LocalCurrencySettingsContent
- ModifierMissing:LogsScreen.kt$LogDetailScreen
- ModifierMissing:LogsScreen.kt$LogsScreen
- ModifierMissing:Money.kt$MoneyDisplay
- ModifierMissing:OnboardingSlidesScreen.kt$OnboardingSlidesScreen
- ModifierMissing:PinChooseScreen.kt$PinChooseScreen
- ModifierMissing:PinSheet.kt$PinSheet
- ModifierMissing:PriceEditScreen.kt$PriceEditContent
- ModifierMissing:PricePreviewScreen.kt$PricePreviewContent
- ModifierMissing:ProfileIntroScreen.kt$ProfileIntroScreen
- ModifierMissing:QrScanningScreen.kt$QrScanningScreen
- ModifierMissing:QuickPaySettingsScreen.kt$QuickPaySettingsScreenContent
- ModifierMissing:ReceiveSheet.kt$ReceiveSheet
- ModifierMissing:ReportIssueResultScreen.kt$ReportIssueResultScreen
- ModifierMissing:ReportIssueScreen.kt$ReportIssueContent
- ModifierMissing:RestoreWalletScreen.kt$MnemonicInputField
- ModifierMissing:SavingsAdvancedScreen.kt$ChannelItem
- ModifierMissing:SavingsAvailabilityScreen.kt$SavingsAvailabilityScreen
- ModifierMissing:SavingsIntroScreen.kt$SavingsIntroScreen
- ModifierMissing:SavingsWalletScreen.kt$SavingsWalletScreen
- ModifierMissing:SendSheet.kt$SendSheet
- ModifierMissing:SettingsScreen.kt$SettingsScreenContent
- ModifierMissing:SheetHost.kt$SheetHost
- ModifierMissing:ShopDiscoverScreen.kt$ShopDiscoverScreen
- ModifierMissing:ShopIntroScreen.kt$ShopIntroScreen
- ModifierMissing:ShopWebViewScreen.kt$ShopWebViewScreen
- ModifierMissing:Spacers.kt$FillHeight
- ModifierMissing:Spacers.kt$FillWidth
- ModifierMissing:Spacers.kt$HorizontalSpacer
- ModifierMissing:Spacers.kt$VerticalSpacer
- ModifierMissing:SpendingIntroScreen.kt$SpendingIntroScreen
- ModifierMissing:SpendingWalletScreen.kt$SpendingWalletScreen
- ModifierMissing:SplashScreen.kt$SplashScreen
- ModifierMissing:TermsOfUseScreen.kt$TermsOfUseScreen
- ModifierMissing:ToastView.kt$ToastView
- ModifierMissing:TransferIntroScreen.kt$TransferIntroScreen
- ModifierMissing:WarningMultipleDevicesScreen.kt$WarningMultipleDevicesScreen
- ModifierMissing:WeatherEditScreen.kt$WeatherEditContent
- ModifierMissing:WeatherPreviewScreen.kt$WeatherPreviewContent
- ModifierMissing:WidgetsIntroScreen.kt$WidgetsIntroScreen
- ModifierNotUsedAtRoot:SettingsTextButtonRow.kt$modifier = modifier.then(if (!enabled) Modifier.alpha(0.5f) else Modifier)
- ModifierWithoutDefault:ReceiveQrScreen.kt$modifier
- ModifierWithoutDefault:SyncNodeView.kt$modifier
- ModifierWithoutDefault:WalletBalanceView.kt$modifier
- MultipleEmitters:ActivityExploreScreen.kt$LightningDetails
- MultipleEmitters:DrawerMenu.kt$DrawerMenu
- MultipleEmitters:SendConfirmScreen.kt$LnurlCommentSection
- MultipleEmitters:SendConfirmScreen.kt$TagsSection
- MutableStateAutoboxing:DragDropColumn.kt$mutableStateOf(0f)
- MutableStateParam:ReceiveQrScreen.kt$cjitActive
- MutableStateParam:ReceiveQrScreen.kt$cjitInvoice
NestedBlockDepth:Context.kt$fun Context.copyAssetToStorage(asset: String, dest: String)
NestedBlockDepth:LogsRepo.kt$LogsRepo$private fun createZipBase64(logFiles: List<LogFile>): String
NestedBlockDepth:MonetaryVisualTransformation.kt$MonetaryVisualTransformation$private fun createOffsetMapping(original: String, transformed: String): OffsetMapping
NestedBlockDepth:ShopWebViewInterface.kt$ShopWebViewInterface$@JavascriptInterface fun postMessage(message: String)
+ NoUnusedImports:ActivityDetailScreen.kt$to.bitkit.ui.screens.wallets.activity.ActivityDetailScreen.kt
NoWildcardImports:LightningChannel.kt$import androidx.compose.foundation.layout.*
- ParameterNaming:AddTagScreen.kt$onInputUpdated
- ParameterNaming:AddTagScreen.kt$onTagConfirmed
- ParameterNaming:AddTagScreen.kt$onTagSelected
- ParameterNaming:AddWidgetsScreen.kt$onWidgetSelected
- ParameterNaming:AddressViewerScreen.kt$onAddressSelected
- ParameterNaming:AddressViewerScreen.kt$onSearchTextChanged
- ParameterNaming:BiometricPrompt.kt$onFailed
- ParameterNaming:BiometricPrompt.kt$onUnsupported
- ParameterNaming:EditInvoiceScreen.kt$onTextChanged
- ParameterNaming:ExternalConnectionScreen.kt$onNodeConnected
- ParameterNaming:FundingScreen.kt$onAdvanced
- ParameterNaming:LnurlChannelScreen.kt$onConnected
- ParameterNaming:LocationBlockScreen.kt$onBackPressed
- ParameterNaming:PinChooseScreen.kt$onPinChosen
- ParameterNaming:PinConfirmScreen.kt$onPinConfirmed
- ParameterNaming:QrCodeImage.kt$onBitmapGenerated
- ParameterNaming:ReceiveAmountScreen.kt$onCjitCreated
- ParameterNaming:SpendingAdvancedScreen.kt$onOrderCreated
- ParameterNaming:SpendingAmountScreen.kt$onOrderCreated
- ParameterNaming:TransactionSpeedSettingsScreen.kt$onSpeedSelected
- PreviewPublic:CameraPermissionView.kt$PreviewDenied
- PreviewPublic:CameraPermissionView.kt$PreviewInSheet
- PreviewPublic:CameraPermissionView.kt$PreviewRequired
- PreviewPublic:EmptyWalletView.kt$EmptyStateViewPreview
- PreviewPublic:HighlightLabel.kt$FlexibleLogoPreview
- PreviewPublic:HighlightLabel.kt$LongTextLogoPreview
- PreviewPublic:SplashScreen.kt$SplashScreenPreview
PrintStackTrace:ShareSheet.kt$e
ReturnCount:AppViewModel.kt$AppViewModel$private suspend fun handleSanityChecks(amountSats: ULong)
ReturnCount:FcmService.kt$FcmService$private fun decryptPayload(response: EncryptedNotification)
ReturnCount:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$private fun findUpdatedChannel( currentChannel: ChannelDetails, allChannels: List<ChannelDetails>, ): ChannelDetails?
SwallowedException:Crypto.kt$Crypto$e: Exception
TooGenericExceptionCaught:ActivityDetailViewModel.kt$ActivityDetailViewModel$e: Exception
- TooGenericExceptionCaught:ActivityDetailViewModel.kt$ActivityDetailViewModel$e: Throwable
TooGenericExceptionCaught:ActivityRepo.kt$ActivityRepo$e: Exception
- TooGenericExceptionCaught:AddressChecker.kt$AddressChecker$e: Exception
TooGenericExceptionCaught:AppViewModel.kt$AppViewModel$e: Exception
TooGenericExceptionCaught:ArticleModel.kt$e: Exception
TooGenericExceptionCaught:BackupNavSheetViewModel.kt$BackupNavSheetViewModel$e: Throwable
@@ -426,19 +169,16 @@
TooGenericExceptionCaught:BlocktankRepo.kt$BlocktankRepo$e: Throwable
TooGenericExceptionCaught:BoostTransactionViewModel.kt$BoostTransactionViewModel$e: Exception
TooGenericExceptionCaught:ChannelOrdersScreen.kt$e: Throwable
- TooGenericExceptionCaught:ContentView.kt$e: Throwable
TooGenericExceptionCaught:CoreService.kt$ActivityService$e: Exception
TooGenericExceptionCaught:CoreService.kt$CoreService$e: Exception
TooGenericExceptionCaught:Crypto.kt$Crypto$e: Exception
TooGenericExceptionCaught:CurrencyRepo.kt$CurrencyRepo$e: Exception
TooGenericExceptionCaught:CurrencyService.kt$CurrencyService$e: Exception
TooGenericExceptionCaught:ElectrumConfigViewModel.kt$ElectrumConfigViewModel$e: Exception
- TooGenericExceptionCaught:LightningConnectionsViewModel.kt$LightningConnectionsViewModel$e: Exception
TooGenericExceptionCaught:LightningRepo.kt$LightningRepo$e: Throwable
TooGenericExceptionCaught:LightningService.kt$LightningService$e: Exception
TooGenericExceptionCaught:LogsRepo.kt$LogsRepo$e: Exception
TooGenericExceptionCaught:LogsViewModel.kt$LogsViewModel$e: Exception
- TooGenericExceptionCaught:NewTransactionSheetDetails.kt$NewTransactionSheetDetails.Companion$e: Exception
TooGenericExceptionCaught:PriceService.kt$PriceService$e: Exception
TooGenericExceptionCaught:QrScanningScreen.kt$e: Exception
TooGenericExceptionCaught:SendCoinSelectionViewModel.kt$SendCoinSelectionViewModel$e: Throwable
@@ -467,7 +207,6 @@
TooManyFunctions:LightningRepo.kt$LightningRepo
TooManyFunctions:LightningService.kt$LightningService : BaseCoroutineScope
TooManyFunctions:SettingsViewModel.kt$SettingsViewModel : ViewModel
- TooManyFunctions:TagMetadataDao.kt$TagMetadataDao
TooManyFunctions:Text.kt$to.bitkit.ui.components.Text.kt
TooManyFunctions:TransferViewModel.kt$TransferViewModel : ViewModel
TooManyFunctions:WalletRepo.kt$WalletRepo
@@ -476,7 +215,6 @@
TooManyFunctions:WidgetsStore.kt$WidgetsStore
TopLevelPropertyNaming:DrawerMenu.kt$private const val zIndexMenu = 11f
TopLevelPropertyNaming:DrawerMenu.kt$private const val zIndexScrim = 10f
- UnusedPrivateProperty:CurrencyRepoTest.kt$CurrencyRepoTest$private val toastEventBus: ToastEventBus = mock()
WildcardImport:LightningChannel.kt$import androidx.compose.foundation.layout.*
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
index e9d6d043e..906a21eba 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
@@ -30,7 +30,6 @@ class BlocksEditScreenTest {
@Test
fun testBlocksEditScreenWithDefaultPreferences() {
// Arrange
- var closeClicked = false
var backClicked = false
var blockClicked = false
var timeClicked = false
@@ -45,7 +44,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickShowBlock = { blockClicked = true },
onClickShowTime = { timeClicked = true },
@@ -111,7 +109,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
@@ -143,7 +140,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
@@ -177,7 +173,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
@@ -220,7 +215,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = { blockClicked = true },
onClickShowTime = { timeClicked = true },
@@ -277,7 +271,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
@@ -305,7 +298,6 @@ class BlocksEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksEditContent(
- onClose = {},
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
index 5f2f0aca8..3a5393d50 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
@@ -28,7 +28,6 @@ class BlocksPreviewContentTest {
@Test
fun testBlocksPreviewWithEnabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -38,7 +37,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -85,7 +83,6 @@ class BlocksPreviewContentTest {
@Test
fun testBlocksPreviewWithDisabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -95,7 +92,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -137,7 +133,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -162,7 +157,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -194,13 +188,11 @@ class BlocksPreviewContentTest {
@Test
fun testNavigationCallbacks() {
// Arrange
- var closeClicked = false
var backClicked = false
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = {},
onClickDelete = {},
@@ -232,7 +224,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -258,7 +249,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -291,7 +281,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -314,7 +303,6 @@ class BlocksPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
BlocksPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsEditScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsEditScreenTest.kt
index ab799b484..36d1d5ebf 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsEditScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsEditScreenTest.kt
@@ -21,7 +21,6 @@ class FactsEditContentTest {
@Test
fun testFactsEditScreenWithDefaultPreferences() {
// Arrange
- var closeClicked = false
var backClicked = false
var sourceClicked = false
var resetClicked = false
@@ -31,7 +30,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickShowSource = { sourceClicked = true },
onClickReset = { resetClicked = true },
@@ -93,7 +91,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = {},
onBack = {},
onClickShowSource = {},
onClickReset = { resetClicked = true },
@@ -118,7 +115,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = {},
onBack = {},
onClickShowSource = {},
onClickReset = {},
@@ -139,7 +135,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = {},
onBack = {},
onClickShowSource = {},
onClickReset = {},
@@ -178,7 +173,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = {},
onBack = {},
onClickShowSource = {},
onClickReset = {},
@@ -200,7 +194,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = {},
onBack = {},
onClickShowSource = {},
onClickReset = {},
@@ -217,7 +210,6 @@ class FactsEditContentTest {
@Test
fun testAllCallbacksTriggered() {
// Arrange
- var closeClicked = false
var backClicked = false
var sourceClicked = false
var resetClicked = false
@@ -228,7 +220,6 @@ class FactsEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickShowSource = { sourceClicked = true },
onClickReset = { resetClicked = true },
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsPreviewScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsPreviewScreenTest.kt
index 960638e33..14c269e49 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsPreviewScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/facts/FactsPreviewScreenTest.kt
@@ -20,7 +20,6 @@ class FactsPreviewContentTest {
@Test
fun testFactsPreviewWithEnabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -30,7 +29,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -77,7 +75,6 @@ class FactsPreviewContentTest {
@Test
fun testFactsPreviewWithDisabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -87,7 +84,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -122,7 +118,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -147,7 +142,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -179,13 +173,11 @@ class FactsPreviewContentTest {
@Test
fun testNavigationCallbacks() {
// Arrange
- var closeClicked = false
var backClicked = false
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = {},
onClickDelete = {},
@@ -210,7 +202,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -236,7 +227,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -262,7 +252,6 @@ class FactsPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
FactsPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesEditContentTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesEditContentTest.kt
index f78d729a8..b3ff9239e 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesEditContentTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesEditContentTest.kt
@@ -31,7 +31,6 @@ class HeadlinesEditContentTest {
@Test
fun testHeadlinesEditScreenWithDefaultPreferences() {
// Arrange
- var closeClicked = false
var backClicked = false
var timeClicked = false
var resetClicked = false
@@ -42,7 +41,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickTime = { timeClicked = true },
onClickReset = { resetClicked = true },
@@ -118,7 +116,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = { resetClicked = true },
@@ -150,7 +147,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = {},
@@ -177,7 +173,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = {},
@@ -199,7 +194,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = {},
@@ -246,7 +240,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = {},
@@ -272,7 +265,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = {},
onBack = {},
onClickTime = {},
onClickReset = {},
@@ -290,7 +282,6 @@ class HeadlinesEditContentTest {
@Test
fun testAllCallbacksTriggered() {
// Arrange
- var closeClicked = false
var backClicked = false
var timeClicked = false
var resetClicked = false
@@ -305,7 +296,6 @@ class HeadlinesEditContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickTime = { timeClicked = true },
onClickReset = { resetClicked = true },
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesPreviewContentTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesPreviewContentTest.kt
index e037640b7..a69371fc4 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesPreviewContentTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/headlines/HeadlinesPreviewContentTest.kt
@@ -29,7 +29,6 @@ class HeadlinesPreviewContentTest {
@Test
fun testHeadlinesPreviewWithImplementedHeadlines() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -39,7 +38,6 @@ class HeadlinesPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -86,7 +84,6 @@ class HeadlinesPreviewContentTest {
@Test
fun testHeadlinesPreviewWithoutImplementedHeadlines() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -96,7 +93,6 @@ class HeadlinesPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -134,7 +130,6 @@ class HeadlinesPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -159,7 +154,6 @@ class HeadlinesPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -191,13 +185,11 @@ class HeadlinesPreviewContentTest {
@Test
fun testNavigationCallbacks() {
// Arrange
- var closeClicked = false
var backClicked = false
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = {},
onClickDelete = {},
@@ -223,7 +215,6 @@ class HeadlinesPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
HeadlinesPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherEditScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherEditScreenTest.kt
index df07f2497..b58f05596 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherEditScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherEditScreenTest.kt
@@ -31,7 +31,6 @@ class WeatherEditScreenTest {
@Test
fun testWeatherEditScreenWithDefaultPreferences() {
// Arrange
- var closeClicked = false
var backClicked = false
var titleClicked = false
var descriptionClicked = false
@@ -44,7 +43,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickShowTitle = { titleClicked = true },
onClickShowDescription = { descriptionClicked = true },
@@ -106,7 +104,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
@@ -136,7 +133,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
@@ -166,7 +162,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
@@ -203,7 +198,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = { titleClicked = true },
onClickShowDescription = { descriptionClicked = true },
@@ -251,7 +245,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
@@ -277,7 +270,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
@@ -326,7 +318,6 @@ class WeatherEditScreenTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherEditContent(
- onClose = {},
onBack = {},
onClickShowTitle = {},
onClickShowDescription = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherPreviewScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherPreviewScreenTest.kt
index 8c1de2847..be8dee972 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherPreviewScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/weather/WeatherPreviewScreenTest.kt
@@ -30,7 +30,6 @@ class WeatherPreviewContentTest {
@Test
fun testWeatherPreviewWithEnabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -40,7 +39,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -87,7 +85,6 @@ class WeatherPreviewContentTest {
@Test
fun testWeatherPreviewWithDisabledWidget() {
// Arrange
- var closeClicked = false
var backClicked = false
var editClicked = false
var deleteClicked = false
@@ -97,7 +94,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
@@ -137,7 +133,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -162,7 +157,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -194,13 +188,11 @@ class WeatherPreviewContentTest {
@Test
fun testNavigationCallbacks() {
// Arrange
- var closeClicked = false
var backClicked = false
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = { closeClicked = true },
onBack = { backClicked = true },
onClickEdit = {},
onClickDelete = {},
@@ -230,7 +222,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -256,7 +247,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -287,7 +277,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -310,7 +299,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
@@ -333,7 +321,6 @@ class WeatherPreviewContentTest {
composeTestRule.setContent {
AppThemeSurface {
WeatherPreviewContent(
- onClose = {},
onBack = {},
onClickEdit = {},
onClickDelete = {},
diff --git a/app/src/androidTest/java/to/bitkit/ui/settings/support/ReportIssueScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/settings/support/ReportIssueScreenTest.kt
index 845e0ed55..d7e9f2c2b 100644
--- a/app/src/androidTest/java/to/bitkit/ui/settings/support/ReportIssueScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/settings/support/ReportIssueScreenTest.kt
@@ -29,7 +29,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = {},
onUpdateMessage = {},
@@ -54,7 +53,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = { emailUpdated = it },
onUpdateMessage = {},
@@ -77,7 +75,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = {},
onUpdateMessage = { messageUpdated = it },
@@ -100,7 +97,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = { sendClicked = true },
onUpdateEmail = {},
onUpdateMessage = {},
@@ -121,7 +117,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = {},
onUpdateMessage = {},
@@ -140,7 +135,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = {},
onUpdateMessage = {},
@@ -163,7 +157,6 @@ class ReportIssueContentTest {
AppThemeSurface {
ReportIssueContent(
onBack = {},
- onClose = {},
onConfirm = {},
onUpdateEmail = {},
onUpdateMessage = {},
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d9daa8a76..e4693f14b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,11 @@
+
+
+ android:exported="false" />
+
+
-
+
+
+
-
-
@@ -90,16 +101,20 @@
+
+
-
-
diff --git a/app/src/main/java/to/bitkit/di/EnvModule.kt b/app/src/main/java/to/bitkit/di/EnvModule.kt
index f7b3c187c..7bd559988 100644
--- a/app/src/main/java/to/bitkit/di/EnvModule.kt
+++ b/app/src/main/java/to/bitkit/di/EnvModule.kt
@@ -6,9 +6,10 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.Network
import to.bitkit.env.Env
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
@Module
@InstallIn(SingletonComponent::class)
@@ -19,6 +20,7 @@ object EnvModule {
return Env.network
}
+ @OptIn(ExperimentalTime::class)
@Provides
fun provideClock(): Clock {
return Clock.System
diff --git a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
index 576422178..c9ad3869e 100644
--- a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
+++ b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
@@ -25,8 +25,8 @@ import javax.inject.Singleton
@Singleton
class NotifyPaymentReceivedHandler @Inject constructor(
- @param:ApplicationContext private val context: Context,
- @param:IoDispatcher private val ioDispatcher: CoroutineDispatcher,
+ @ApplicationContext private val context: Context,
+ @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val activityRepo: ActivityRepo,
private val currencyRepo: CurrencyRepo,
private val settingsStore: SettingsStore,
diff --git a/app/src/main/java/to/bitkit/ext/Activities.kt b/app/src/main/java/to/bitkit/ext/Activities.kt
index 73153cf40..9b960ffbe 100644
--- a/app/src/main/java/to/bitkit/ext/Activities.kt
+++ b/app/src/main/java/to/bitkit/ext/Activities.kt
@@ -1,6 +1,8 @@
package to.bitkit.ext
import com.synonym.bitkitcore.Activity
+import com.synonym.bitkitcore.LightningActivity
+import com.synonym.bitkitcore.OnchainActivity
import com.synonym.bitkitcore.PaymentState
import com.synonym.bitkitcore.PaymentType
@@ -70,3 +72,75 @@ fun Activity.Onchain.boostType() = when (this.v1.txType) {
}
enum class BoostType { RBF, CPFP }
+
+@Suppress("LongParameterList")
+fun LightningActivity.Companion.create(
+ id: String,
+ txType: PaymentType,
+ status: PaymentState,
+ value: ULong,
+ invoice: String,
+ timestamp: ULong,
+ fee: ULong = 0u,
+ message: String = "",
+ preimage: String? = null,
+ createdAt: ULong? = timestamp,
+ updatedAt: ULong? = createdAt,
+ seenAt: ULong? = null,
+) = LightningActivity(
+ id = id,
+ txType = txType,
+ status = status,
+ value = value,
+ fee = fee,
+ invoice = invoice,
+ message = message,
+ timestamp = timestamp,
+ preimage = preimage,
+ createdAt = createdAt,
+ updatedAt = updatedAt,
+ seenAt = seenAt,
+)
+
+@Suppress("LongParameterList")
+fun OnchainActivity.Companion.create(
+ id: String,
+ txType: PaymentType,
+ txId: String,
+ value: ULong,
+ fee: ULong,
+ address: String,
+ timestamp: ULong,
+ confirmed: Boolean = false,
+ feeRate: ULong = 1u,
+ isBoosted: Boolean = false,
+ boostTxIds: List = emptyList(),
+ isTransfer: Boolean = false,
+ doesExist: Boolean = true,
+ confirmTimestamp: ULong? = null,
+ channelId: String? = null,
+ transferTxId: String? = null,
+ createdAt: ULong? = timestamp,
+ updatedAt: ULong? = createdAt,
+ seenAt: ULong? = null,
+) = OnchainActivity(
+ id = id,
+ txType = txType,
+ txId = txId,
+ value = value,
+ fee = fee,
+ feeRate = feeRate,
+ address = address,
+ confirmed = confirmed,
+ timestamp = timestamp,
+ isBoosted = isBoosted,
+ boostTxIds = boostTxIds,
+ isTransfer = isTransfer,
+ doesExist = doesExist,
+ confirmTimestamp = confirmTimestamp,
+ channelId = channelId,
+ transferTxId = transferTxId,
+ createdAt = createdAt,
+ updatedAt = updatedAt,
+ seenAt = seenAt,
+)
diff --git a/app/src/main/java/to/bitkit/ext/ByteArray.kt b/app/src/main/java/to/bitkit/ext/ByteArray.kt
index 1246163b2..7aa91d0d5 100644
--- a/app/src/main/java/to/bitkit/ext/ByteArray.kt
+++ b/app/src/main/java/to/bitkit/ext/ByteArray.kt
@@ -12,13 +12,11 @@ fun ByteArray.toHex(): String = this.toHexString()
fun String.fromHex(): ByteArray = this.hexToByteArray()
// endregion
-// region base64
@OptIn(ExperimentalEncodingApi::class)
fun ByteArray.toBase64(): String = Base64.encode(this)
@OptIn(ExperimentalEncodingApi::class)
fun String.fromBase64(): ByteArray = Base64.decode(this)
-// endregion
val String.uByteList get() = this.toByteArray().map { it.toUByte() }
diff --git a/app/src/main/java/to/bitkit/ext/DateTime.kt b/app/src/main/java/to/bitkit/ext/DateTime.kt
index ebf9fabc0..bea254c2b 100644
--- a/app/src/main/java/to/bitkit/ext/DateTime.kt
+++ b/app/src/main/java/to/bitkit/ext/DateTime.kt
@@ -10,11 +10,12 @@ import android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit
import android.icu.text.RelativeDateTimeFormatter.Direction
import android.icu.text.RelativeDateTimeFormatter.RelativeUnit
import android.icu.util.ULocale
-import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atStartOfDayIn
+import kotlinx.datetime.number
import kotlinx.datetime.toJavaLocalDate
+import kotlinx.datetime.toJavaMonth
import kotlinx.datetime.toKotlinLocalDate
import kotlinx.datetime.toLocalDateTime
import java.text.SimpleDateFormat
@@ -26,9 +27,13 @@ import java.time.temporal.ChronoUnit
import java.util.Calendar
import java.util.Date
import java.util.Locale
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant as KInstant
+@OptIn(ExperimentalTime::class)
fun nowMillis(clock: Clock = Clock.System): Long = clock.now().toEpochMilliseconds()
fun nowTimestamp(): Instant = Instant.now().truncatedTo(ChronoUnit.SECONDS)
@@ -63,6 +68,7 @@ fun Long.toLocalizedTimestamp(): String {
}
@Suppress("LongMethod")
+@OptIn(ExperimentalTime::class)
fun Long.toRelativeTimeString(
locale: Locale = Locale.getDefault(),
clock: Clock = Clock.System,
@@ -102,7 +108,8 @@ fun Long.toRelativeTimeString(
fun getDaysInMonth(month: LocalDate): List {
val firstDayOfMonth = LocalDate(month.year, month.month, Constants.FIRST_DAY_OF_MONTH)
- val daysInMonth = month.month.length(isLeapYear(month.year))
+ // FIXME fix month.number
+ val daysInMonth = month.month.toJavaMonth().length(isLeapYear(month.year))
// Get the day of week for the first day (1 = Monday, 7 = Sunday)
val firstDayOfWeek = firstDayOfMonth.dayOfWeek.ordinal + CalendarConstants.CALENDAR_WEEK_OFFSET
@@ -136,6 +143,7 @@ fun isLeapYear(year: Int): Boolean {
(year % Constants.LEAP_YEAR_DIVISOR_400 == 0)
}
+@OptIn(ExperimentalTime::class)
fun isDateInRange(
dateMillis: Long,
startMillis: Long?,
@@ -145,9 +153,9 @@ fun isDateInRange(
if (startMillis == null) return false
val end = endMillis ?: startMillis
- val normalizedDate = kotlinx.datetime.Instant.fromEpochMilliseconds(dateMillis).toLocalDateTime(zone).date
- val normalizedStart = kotlinx.datetime.Instant.fromEpochMilliseconds(startMillis).toLocalDateTime(zone).date
- val normalizedEnd = kotlinx.datetime.Instant.fromEpochMilliseconds(end).toLocalDateTime(zone).date
+ val normalizedDate = KInstant.fromEpochMilliseconds(dateMillis).toLocalDateTime(zone).date
+ val normalizedStart = KInstant.fromEpochMilliseconds(startMillis).toLocalDateTime(zone).date
+ val normalizedEnd = KInstant.fromEpochMilliseconds(end).toLocalDateTime(zone).date
return normalizedDate in normalizedStart..normalizedEnd
}
@@ -155,7 +163,7 @@ fun isDateInRange(
fun LocalDate.toMonthYearString(locale: Locale = Locale.getDefault()): String {
val formatter = SimpleDateFormat(DatePattern.MONTH_YEAR_FORMAT, locale)
val calendar = Calendar.getInstance()
- calendar.set(year, monthNumber - CalendarConstants.MONTH_INDEX_OFFSET, Constants.FIRST_DAY_OF_MONTH)
+ calendar.set(year, month.number - CalendarConstants.MONTH_INDEX_OFFSET, Constants.FIRST_DAY_OF_MONTH)
return formatter.format(calendar.time)
}
@@ -167,6 +175,7 @@ fun LocalDate.plusMonths(months: Int): LocalDate =
toJavaLocalDate().plusMonths(months.toLong()).withDayOfMonth(1) // Always use first day of month for display
.toKotlinLocalDate()
+@OptIn(ExperimentalTime::class)
fun LocalDate.endOfDay(zone: TimeZone = TimeZone.currentSystemDefault()): Long =
atStartOfDayIn(zone).plus(1.days).minus(1.milliseconds).toEpochMilliseconds()
diff --git a/app/src/main/java/to/bitkit/models/Currency.kt b/app/src/main/java/to/bitkit/models/Currency.kt
index ebef4fd36..1b5774e7f 100644
--- a/app/src/main/java/to/bitkit/models/Currency.kt
+++ b/app/src/main/java/to/bitkit/models/Currency.kt
@@ -1,12 +1,13 @@
package to.bitkit.models
-import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
const val STUB_RATE = 115_150.0
const val BITCOIN_SYMBOL = "₿"
@@ -37,11 +38,10 @@ data class FxRate(
val currencyFlag: String,
val lastUpdatedAt: Long,
) {
- val rate: Double
- get() = lastPrice.toDoubleOrNull() ?: 0.0
+ val rate: Double get() = lastPrice.toDoubleOrNull() ?: 0.0
- val timestamp: Instant
- get() = Instant.fromEpochMilliseconds(lastUpdatedAt)
+ @OptIn(ExperimentalTime::class)
+ val timestamp: Instant get() = Instant.fromEpochMilliseconds(lastUpdatedAt)
}
/** aka. Unit */
diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt
index 0f7b6382c..804845a5c 100644
--- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt
@@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.ChannelDetails
import org.lightningdevkit.ldknode.PaymentDetails
import org.lightningdevkit.ldknode.PaymentDirection
@@ -40,11 +39,14 @@ import to.bitkit.services.CoreService
import to.bitkit.utils.Logger
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
private const val SYNC_TIMEOUT_MS = 40_000L
-@Singleton
@Suppress("LargeClass", "LongParameterList")
+@OptIn(ExperimentalTime::class)
+@Singleton
class ActivityRepo @Inject constructor(
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
private val coreService: CoreService,
@@ -138,7 +140,7 @@ class ActivityRepo @Inject constructor(
}
}
- private suspend fun findOpenChannelForTransaction(txid: String): String? {
+ private fun findOpenChannelForTransaction(txid: String): String? {
return try {
val channels = lightningRepo.lightningState.value.channels
if (channels.isEmpty()) return null
@@ -183,7 +185,7 @@ class ActivityRepo @Inject constructor(
}
/**
- * Checks if a transaction was replaced (RBF) by checking if the activity exists but doesExist=false.
+ * Checks if a transaction was replaced (RBF) by checking if the activity exists but `doesExist=false`.
*/
suspend fun wasTransactionReplaced(txid: String): Boolean = withContext(bgDispatcher) {
val onchainActivity = getOnchainActivityByTxId(txid) ?: return@withContext false
@@ -374,7 +376,7 @@ class ActivityRepo @Inject constructor(
/**
* Updates an activity and marks the old one as removed from mempool (for RBF).
* In case of failure in the update or marking as removed, the data will be cached
- * to try again on the next sync
+ * to try again on the next sync.
*/
suspend fun replaceActivity(
id: String,
@@ -460,9 +462,6 @@ class ActivityRepo @Inject constructor(
}.awaitAll()
}
- /**
- * Deletes an activity
- */
suspend fun deleteActivity(id: String): Result = withContext(bgDispatcher) {
return@withContext runCatching {
val deleted = coreService.activity.delete(id)
@@ -477,9 +476,6 @@ class ActivityRepo @Inject constructor(
}
}
- /**
- * Inserts a new activity
- */
suspend fun insertActivity(activity: Activity): Result = withContext(bgDispatcher) {
return@withContext runCatching {
if (activity.rawId() in cacheStore.data.first().deletedActivities) {
@@ -493,9 +489,6 @@ class ActivityRepo @Inject constructor(
}
}
- /**
- * Upserts an activity (insert or update if exists)
- */
suspend fun upsertActivity(activity: Activity): Result = withContext(bgDispatcher) {
return@withContext runCatching {
if (activity.rawId() in cacheStore.data.first().deletedActivities) {
@@ -510,7 +503,7 @@ class ActivityRepo @Inject constructor(
}
/**
- * Inserts a new activity for a fulfilled (channel ready) cjit channel order
+ * Inserts a new activity for a fulfilled (channel ready) CJIT order
*/
suspend fun insertActivityFromCjit(
cjitEntry: IcJitEntry?,
@@ -536,6 +529,7 @@ class ActivityRepo @Inject constructor(
preimage = null,
createdAt = now,
updatedAt = null,
+ seenAt = null, // TODO implement synonymdev/bitkit-ios#270 changes
)
)
)
diff --git a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt
index 2e98b443c..b3e147b04 100644
--- a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt
@@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import to.bitkit.R
import to.bitkit.data.AppDb
import to.bitkit.data.CacheStore
@@ -49,6 +48,8 @@ import to.bitkit.utils.jsonLogOf
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
/**
* Manages backup & restore of wallet metadata to a remote VSS server.
@@ -65,10 +66,11 @@ import javax.inject.Singleton
* ```
*/
@Suppress("LongParameterList")
+@OptIn(ExperimentalTime::class)
@Singleton
class BackupRepo @Inject constructor(
- @param:ApplicationContext private val context: Context,
- @param:IoDispatcher private val ioDispatcher: CoroutineDispatcher,
+ @ApplicationContext private val context: Context,
+ @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val cacheStore: CacheStore,
private val vssBackupClient: VssBackupClient,
private val settingsStore: SettingsStore,
diff --git a/app/src/main/java/to/bitkit/repositories/CurrencyRepo.kt b/app/src/main/java/to/bitkit/repositories/CurrencyRepo.kt
index a8629a23b..82a39095e 100644
--- a/app/src/main/java/to/bitkit/repositories/CurrencyRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/CurrencyRepo.kt
@@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.di.BgDispatcher
@@ -41,16 +40,19 @@ import java.math.RoundingMode
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
@Suppress("TooManyFunctions")
+@OptIn(ExperimentalTime::class)
@Singleton
class CurrencyRepo @Inject constructor(
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
private val currencyService: CurrencyService,
private val settingsStore: SettingsStore,
private val cacheStore: CacheStore,
- @Named("enablePolling") private val enablePolling: Boolean,
private val clock: Clock,
+ @Named("enablePolling") private val enablePolling: Boolean,
) : AmountInputHandler {
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
private val _currencyState = MutableStateFlow(CurrencyState())
diff --git a/app/src/main/java/to/bitkit/repositories/HealthRepo.kt b/app/src/main/java/to/bitkit/repositories/HealthRepo.kt
index 72b9a211d..56e8e85ee 100644
--- a/app/src/main/java/to/bitkit/repositories/HealthRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/HealthRepo.kt
@@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-import kotlinx.datetime.Clock
import to.bitkit.data.CacheStore
import to.bitkit.di.BgDispatcher
import to.bitkit.models.BackupCategory
@@ -19,8 +18,11 @@ import to.bitkit.models.BackupItemStatus
import to.bitkit.models.HealthState
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Singleton
class HealthRepo @Inject constructor(
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt
index 2d9c35dfd..5163ca0e9 100644
--- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt
@@ -21,15 +21,16 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import org.lightningdevkit.ldknode.Address
import org.lightningdevkit.ldknode.BalanceDetails
import org.lightningdevkit.ldknode.BestBlock
import org.lightningdevkit.ldknode.ChannelConfig
import org.lightningdevkit.ldknode.ChannelDetails
+import org.lightningdevkit.ldknode.ClosureReason
import org.lightningdevkit.ldknode.Event
import org.lightningdevkit.ldknode.NodeStatus
import org.lightningdevkit.ldknode.PaymentDetails
@@ -61,9 +62,12 @@ import to.bitkit.services.NodeEventHandler
import to.bitkit.utils.AppError
import to.bitkit.utils.Logger
import to.bitkit.utils.ServiceError
+import to.bitkit.utils.errLogOf
import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
@@ -96,6 +100,9 @@ class LightningRepo @Inject constructor(
private val channelCache = ConcurrentHashMap()
+ private val syncMutex = Mutex()
+ private val syncPending = AtomicBoolean(false)
+
/**
* Executes the provided operation only if the node is running.
* If the node is not running, waits for it to be running for a specified timeout.
@@ -152,6 +159,9 @@ class LightningRepo @Inject constructor(
): Result {
return try {
operation()
+ } catch (e: CancellationException) {
+ // Cancellation is expected during pull-to-refresh, rethrow per Kotlin best practices
+ throw e
} catch (e: Throwable) {
Logger.error("$operationName error", e, context = TAG)
Result.failure(e)
@@ -300,24 +310,35 @@ class LightningRepo @Inject constructor(
}
suspend fun sync(): Result = executeWhenNodeRunning("Sync") {
- syncState()
- if (_lightningState.value.isSyncingWallet) {
- Logger.warn("Sync already in progress, waiting for existing sync.", context = TAG)
+ // If sync is in progress, mark pending and skip
+ if (!syncMutex.tryLock()) {
+ syncPending.set(true)
+ Logger.verbose("Sync in progress, pending sync marked", context = TAG)
+ return@executeWhenNodeRunning Result.success(Unit)
}
- withTimeout(SYNC_TIMEOUT_MS) {
- _lightningState.first { !it.isSyncingWallet }
+ try {
+ do {
+ syncPending.set(false)
+ _lightningState.update { it.copy(isSyncingWallet = true) }
+ lightningService.sync()
+ refreshChannelCache()
+ syncState()
+ if (syncPending.get()) delay(SYNC_LOOP_DEBOUNCE_MS)
+ } while (syncPending.getAndSet(false))
+ } finally {
+ _lightningState.update { it.copy(isSyncingWallet = false) }
+ syncMutex.unlock()
}
- _lightningState.update { it.copy(isSyncingWallet = true) }
- lightningService.sync()
- refreshChannelCache()
- syncState()
- _lightningState.update { it.copy(isSyncingWallet = false) }
-
Result.success(Unit)
}
+ /** Clear pending sync flag. Called when manual pull-to-refresh takes priority. */
+ fun clearPendingSync() {
+ syncPending.set(false)
+ }
+
private suspend fun refreshChannelCache() = withContext(bgDispatcher) {
val channels = lightningService.channels ?: return@withContext
channels.forEach { channel ->
@@ -327,39 +348,26 @@ class LightningRepo @Inject constructor(
private fun handleLdkEvent(event: Event) {
when (event) {
- is Event.ChannelPending -> {
- scope.launch {
- refreshChannelCache()
- }
+ is Event.ChannelPending,
+ is Event.ChannelReady -> scope.launch {
+ refreshChannelCache()
}
- is Event.ChannelReady -> {
- scope.launch {
- refreshChannelCache()
- }
- }
-
- is Event.ChannelClosed -> {
- val channelId = event.channelId
- val reason = event.reason?.toString() ?: ""
- scope.launch {
- registerClosedChannel(channelId, reason)
- }
+ is Event.ChannelClosed -> scope.launch {
+ registerClosedChannel(
+ channelId = event.channelId,
+ reason = event.reason,
+ )
}
- else -> {
- // Other events don't need special handling
- }
+ else -> Unit // Other events don't need special handling
}
}
- private suspend fun registerClosedChannel(channelId: String, reason: String?) = withContext(bgDispatcher) {
+ private suspend fun registerClosedChannel(channelId: String, reason: ClosureReason?) = withContext(bgDispatcher) {
try {
val channel = channelCache[channelId] ?: run {
- Logger.error(
- "Could not find channel details for closed channel: channelId=$channelId",
- context = TAG
- )
+ Logger.error("Could not find channel details for closed channel: channelId=$channelId", context = TAG)
return@withContext
}
@@ -391,7 +399,7 @@ class LightningRepo @Inject constructor(
forwardingFeeProportionalMillionths = channel.config.forwardingFeeProportionalMillionths,
forwardingFeeBaseMsat = channel.config.forwardingFeeBaseMsat,
channelName = channelName,
- channelClosureReason = reason.orEmpty()
+ channelClosureReason = reason?.toString().orEmpty(),
)
coreService.activity.upsertClosedChannelList(listOf(closedChannel))
@@ -756,9 +764,11 @@ class LightningRepo @Inject constructor(
utxosToSpend = utxosToSpend,
)
Result.success(fee)
- } catch (_: Throwable) {
+ } catch (e: CancellationException) {
+ throw e
+ } catch (e: Throwable) {
val fallbackFee = 1000uL
- Logger.warn("Error calculating fee, using fallback of $fallbackFee", context = TAG)
+ Logger.warn("Error calculating fee, using fallback of $fallbackFee ${errLogOf(e)}", context = TAG)
Result.success(fallbackFee)
}
}
@@ -772,7 +782,9 @@ class LightningRepo @Inject constructor(
val satsPerVByte = fees.getSatsPerVByteFor(speed)
satsPerVByte.toULong()
}.onFailure { e ->
- Logger.error("Error getFeeRateForSpeed. speed:$speed", e, context = TAG)
+ if (e !is CancellationException) {
+ Logger.error("Error getFeeRateForSpeed. speed:$speed", e, context = TAG)
+ }
}
}
@@ -989,8 +1001,8 @@ class LightningRepo @Inject constructor(
companion object {
private const val TAG = "LightningRepo"
- private const val SYNC_TIMEOUT_MS = 20_000L
private const val CHANNEL_ID_PREVIEW_LENGTH = 10
+ private const val SYNC_LOOP_DEBOUNCE_MS = 500L
}
}
diff --git a/app/src/main/java/to/bitkit/repositories/PreActivityMetadataRepo.kt b/app/src/main/java/to/bitkit/repositories/PreActivityMetadataRepo.kt
index 5b3ca2bb1..d0fa79b79 100644
--- a/app/src/main/java/to/bitkit/repositories/PreActivityMetadataRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/PreActivityMetadataRepo.kt
@@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import to.bitkit.di.IoDispatcher
import to.bitkit.ext.nowMillis
import to.bitkit.ext.nowTimestamp
@@ -15,7 +14,10 @@ import to.bitkit.services.CoreService
import to.bitkit.utils.Logger
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Singleton
class PreActivityMetadataRepo @Inject constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
diff --git a/app/src/main/java/to/bitkit/repositories/TransferRepo.kt b/app/src/main/java/to/bitkit/repositories/TransferRepo.kt
index 4e4596ddf..6aa2e8708 100644
--- a/app/src/main/java/to/bitkit/repositories/TransferRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/TransferRepo.kt
@@ -4,7 +4,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.ChannelDetails
import to.bitkit.data.dao.TransferDao
import to.bitkit.data.entities.TransferEntity
@@ -12,10 +11,13 @@ import to.bitkit.di.BgDispatcher
import to.bitkit.ext.channelId
import to.bitkit.models.TransferType
import to.bitkit.utils.Logger
-import java.util.*
+import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Singleton
class TransferRepo @Inject constructor(
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
@@ -100,8 +102,10 @@ class TransferRepo @Inject constructor(
Logger.debug("Channel $channelId balance swept, settled transfer: ${transfer.id}", context = TAG)
}
}
+ }.onSuccess {
+ Logger.verbose("syncTransferStates completed", context = TAG)
}.onFailure { e ->
- Logger.error("Failed to sync transfer states", e, context = TAG)
+ Logger.error("syncTransferStates error", e, context = TAG)
}
}
diff --git a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt
index f9a6f6370..7e82063d9 100644
--- a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt
@@ -6,8 +6,10 @@ import com.synonym.bitkitcore.Scanner
import com.synonym.bitkitcore.decode
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
@@ -33,8 +35,11 @@ import to.bitkit.usecases.WipeWalletUseCase
import to.bitkit.utils.Bip21Utils
import to.bitkit.utils.Logger
import to.bitkit.utils.ServiceError
+import to.bitkit.utils.errLogOf
+import to.bitkit.utils.measured
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.coroutines.cancellation.CancellationException
@Suppress("LongParameterList")
@Singleton
@@ -48,6 +53,7 @@ class WalletRepo @Inject constructor(
private val preActivityMetadataRepo: PreActivityMetadataRepo,
private val deriveBalanceStateUseCase: DeriveBalanceStateUseCase,
private val wipeWalletUseCase: WipeWalletUseCase,
+ private val transferRepo: TransferRepo,
) {
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
@@ -57,6 +63,8 @@ class WalletRepo @Inject constructor(
private val _balanceState = MutableStateFlow(BalanceState())
val balanceState = _balanceState.asStateFlow()
+ private var eventSyncJob: Job? = null
+
init {
repoScope.launch {
lightningRepo.nodeEvents.collect { event ->
@@ -160,32 +168,60 @@ class WalletRepo @Inject constructor(
preActivityMetadataRepo.addPreActivityMetadata(preActivityMetadata)
}
- suspend fun syncNodeAndWallet(): Result = withContext(bgDispatcher) {
+ suspend fun syncNodeAndWallet(source: SyncSource = SyncSource.AUTO): Result = withContext(bgDispatcher) {
+ if (!lightningRepo.lightningState.value.nodeLifecycleState.isRunning()) {
+ Logger.debug("syncNodeAndWallet skipped: node not running", context = TAG)
+ return@withContext Result.failure(Exception("Node not running"))
+ }
+
+ val sourceLabel = source.name.lowercase()
val startHeight = lightningRepo.lightningState.value.block()?.height
- Logger.verbose("syncNodeAndWallet started at block height=$startHeight", context = TAG)
- syncBalances()
- lightningRepo.sync().onSuccess {
+ Logger.debug("Sync $sourceLabel started at block height=$startHeight", context = TAG)
+
+ val result = measured("Sync $sourceLabel") {
syncBalances()
- val endHeight = lightningRepo.lightningState.value.block()?.height
- Logger.verbose("syncNodeAndWallet completed at block height=$endHeight", context = TAG)
- return@withContext Result.success(Unit)
- }.onFailure { e ->
- if (e is TimeoutCancellationException) {
+ lightningRepo.sync().onSuccess {
syncBalances()
+ }.onFailure { e ->
+ if (e is TimeoutCancellationException) {
+ syncBalances()
+ }
}
- return@withContext Result.failure(e)
}
+
+ val endHeight = lightningRepo.lightningState.value.block()?.height
+ Logger.debug("Sync $sourceLabel completed at block height=$endHeight", context = TAG)
+
+ result
}
suspend fun syncBalances() {
deriveBalanceStateUseCase().onSuccess { balanceState ->
runCatching { cacheStore.cacheBalance(balanceState) }
_balanceState.update { balanceState }
- }.onFailure {
- Logger.warn("Could not sync balances", context = TAG)
+ }.onFailure { e ->
+ if (e !is CancellationException) {
+ Logger.warn("Could not sync balances ${errLogOf(e)}", context = TAG)
+ }
+ }
+ }
+
+ /** Debounce syncs for [Event.SyncCompleted]. Rapid consecutive events are coalesced. */
+ fun debounceSyncByEvent() {
+ eventSyncJob?.cancel()
+ eventSyncJob = repoScope.launch {
+ delay(EVENT_SYNC_DEBOUNCE_MS)
+ syncNodeAndWallet()
+ transferRepo.syncTransferStates()
}
}
+ /** Cancels any pending sync for [Event.SyncCompleted]. Called when manual pull-to-refresh takes priority. */
+ fun cancelSyncByEvent() {
+ eventSyncJob?.cancel()
+ eventSyncJob = null
+ }
+
suspend fun refreshBip21ForEvent(event: Event) = withContext(bgDispatcher) {
when (event) {
is Event.ChannelReady -> {
@@ -570,6 +606,7 @@ class WalletRepo @Inject constructor(
private companion object {
const val TAG = "WalletRepo"
+ const val EVENT_SYNC_DEBOUNCE_MS = 500L
}
}
@@ -583,3 +620,5 @@ data class WalletState(
val receiveOnSpendingBalance: Boolean = true,
val walletExists: Boolean = false,
)
+
+enum class SyncSource { AUTO, MANUAL }
diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt
index 76c621cf2..34b18f06b 100644
--- a/app/src/main/java/to/bitkit/services/CoreService.kt
+++ b/app/src/main/java/to/bitkit/services/CoreService.kt
@@ -69,6 +69,7 @@ import to.bitkit.async.ServiceQueue
import to.bitkit.data.CacheStore
import to.bitkit.env.Env
import to.bitkit.ext.amountSats
+import to.bitkit.ext.create
import to.bitkit.models.toCoreNetwork
import to.bitkit.utils.AppError
import to.bitkit.utils.Logger
@@ -473,18 +474,17 @@ class ActivityService(
status = state
)
} else {
- LightningActivity(
+ LightningActivity.create(
id = payment.id,
txType = payment.direction.toPaymentType(),
status = state,
value = payment.amountSats ?: 0u,
- fee = (payment.feePaidMsat ?: 0u) / 1000u,
invoice = kind.bolt11 ?: "Loading...",
- message = kind.description.orEmpty(),
timestamp = payment.latestUpdateTimestamp,
+ fee = (payment.feePaidMsat ?: 0u) / 1000u,
+ message = kind.description.orEmpty(),
preimage = kind.preimage,
- createdAt = payment.latestUpdateTimestamp,
- updatedAt = payment.latestUpdateTimestamp,
+ seenAt = null, // TODO implement synonymdev/bitkit-ios#270 changes
)
}
@@ -597,25 +597,19 @@ class ActivityService(
): OnchainActivity {
val isTransfer = channelId != null
- return OnchainActivity(
+ return OnchainActivity.create(
id = payment.id,
txType = payment.direction.toPaymentType(),
txId = kind.txid,
value = payment.amountSats ?: 0u,
fee = (payment.feePaidMsat ?: 0u) / 1000u,
- feeRate = 1u,
address = resolvedAddress ?: "Loading...",
- confirmed = confirmationData.isConfirmed,
timestamp = confirmationData.timestamp,
- isBoosted = false,
- boostTxIds = emptyList(),
+ confirmed = confirmationData.isConfirmed,
isTransfer = isTransfer,
- doesExist = true,
confirmTimestamp = confirmationData.confirmedTimestamp,
channelId = channelId,
- transferTxId = null,
- createdAt = confirmationData.timestamp,
- updatedAt = confirmationData.timestamp,
+ seenAt = null, // TODO implement synonymdev/bitkit-ios#270 changes
)
}
@@ -726,42 +720,35 @@ class ActivityService(
if (isLightning) {
id = "test-lightning-$i"
activity = Activity.Lightning(
- LightningActivity(
+ LightningActivity.create(
id = id,
txType = txType,
status = status,
value = value,
- fee = (1..1_000).random().toULong(),
invoice = "lnbc$value",
- message = possibleMessages.random(),
timestamp = txTimestamp,
+ fee = (1..1_000).random().toULong(),
+ message = possibleMessages.random(),
preimage = if (Random.nextBoolean()) "preimage$i" else null,
- createdAt = txTimestamp,
- updatedAt = txTimestamp
)
)
} else {
id = "test-onchain-$i"
activity = Activity.Onchain(
- OnchainActivity(
+ OnchainActivity.create(
id = id,
txType = txType,
txId = "a".repeat(64), // Mock txid
value = value,
fee = (100..10_000).random().toULong(),
- feeRate = (1..100).random().toULong(),
address = "bc1...$i",
- confirmed = Random.nextBoolean(),
timestamp = txTimestamp,
+ confirmed = Random.nextBoolean(),
+ feeRate = (1..100).random().toULong(),
isBoosted = Random.nextBoolean(),
- boostTxIds = emptyList(),
isTransfer = Random.nextBoolean(),
- doesExist = true,
confirmTimestamp = if (Random.nextBoolean()) txTimestamp + 3600.toULong() else null,
channelId = if (Random.nextBoolean()) "channel$i" else null,
- transferTxId = null,
- createdAt = txTimestamp,
- updatedAt = txTimestamp,
)
)
}
diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt
index b69559555..2583081e9 100644
--- a/app/src/main/java/to/bitkit/services/LightningService.kt
+++ b/app/src/main/java/to/bitkit/services/LightningService.kt
@@ -24,7 +24,6 @@ import org.lightningdevkit.ldknode.ElectrumSyncConfig
import org.lightningdevkit.ldknode.Event
import org.lightningdevkit.ldknode.FeeRate
import org.lightningdevkit.ldknode.Node
-import org.lightningdevkit.ldknode.NodeEntropy
import org.lightningdevkit.ldknode.NodeException
import org.lightningdevkit.ldknode.NodeStatus
import org.lightningdevkit.ldknode.PaymentDetails
@@ -118,43 +117,32 @@ class LightningService @Inject constructor(
customRgsServerUrl: String?,
config: Config,
): Node = ServiceQueue.LDK.background {
- val nodeEntropy = NodeEntropy.fromBip39Mnemonic(
- mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name) ?: throw ServiceError.MnemonicNotFound,
- passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name),
- )
-
val builder = Builder.fromConfig(config).apply {
setCustomLogger(LdkLogWriter())
configureChainSource(customServerUrl)
configureGossipSource(customRgsServerUrl)
+ setEntropyBip39Mnemonic(
+ mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name) ?: throw ServiceError.MnemonicNotFound,
+ passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name),
+ )
}
try {
val vssStoreId = vssStoreIdProvider.getVssStoreId(walletIndex)
- val lnurlAuthServerUrl = Env.lnurlAuthServerUrl
val vssUrl = Env.vssServerUrl
- Logger.verbose("Building ldk-node with vssUrl: '$vssUrl'")
- Logger.verbose("Building ldk-node with lnurlAuthServerUrl: '$lnurlAuthServerUrl'")
+ val lnurlAuthServerUrl = Env.lnurlAuthServerUrl
+ val fixedHeaders = emptyMap()
+ Logger.verbose(
+ "Building ldk-node with \n\t vssUrl: '$vssUrl'\n\t lnurlAuthServerUrl: '$lnurlAuthServerUrl'"
+ )
if (lnurlAuthServerUrl.isNotEmpty()) {
- builder.buildWithVssStore(
- vssUrl = vssUrl,
- storeId = vssStoreId,
- lnurlAuthServerUrl = lnurlAuthServerUrl,
- fixedHeaders = emptyMap(),
- nodeEntropy = nodeEntropy,
- )
+ builder.buildWithVssStore(vssUrl, vssStoreId, lnurlAuthServerUrl, fixedHeaders)
} else {
- builder.buildWithVssStoreAndFixedHeaders(
- vssUrl = vssUrl,
- storeId = vssStoreId,
- fixedHeaders = emptyMap(),
- nodeEntropy = nodeEntropy,
- )
+ builder.buildWithVssStoreAndFixedHeaders(vssUrl, vssStoreId, fixedHeaders)
}
} catch (e: BuildException) {
throw LdkError(e)
} finally {
- // cleanup sensitive data
- nodeEntropy.destroy()
+ // TODO: cleanup sensitive data after implementing a `SecureString` value holder for Keychain return values
}
}
@@ -674,7 +662,7 @@ class LightningService @Inject constructor(
): ULong {
val node = this.node ?: throw ServiceError.NodeNotSetup
- Logger.debug(
+ Logger.verbose(
"Calculating fee for $amountSats sats to $address, UTXOs=${utxosToSpend?.size}, satsPerVByte=$satsPerVByte"
)
@@ -686,7 +674,7 @@ class LightningService @Inject constructor(
feeRate = convertVByteToKwu(satsPerVByte),
utxosToSpend = utxosToSpend,
)
- Logger.info("Calculated fee=$fee for $amountSats sats to $address, satsPerVByte=$satsPerVByte")
+ Logger.verbose("Calculated fee=$fee for $amountSats sats to $address, satsPerVByte=$satsPerVByte")
fee
} catch (e: NodeException) {
throw LdkError(e)
diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt
index 7483d6faa..705e1426b 100644
--- a/app/src/main/java/to/bitkit/ui/ContentView.kt
+++ b/app/src/main/java/to/bitkit/ui/ContentView.kt
@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -21,7 +22,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -36,6 +37,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
+import dev.chrisbanes.haze.hazeSource
+import dev.chrisbanes.haze.rememberHazeState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@@ -86,7 +89,9 @@ import to.bitkit.ui.screens.transfer.external.ExternalFeeCustomScreen
import to.bitkit.ui.screens.transfer.external.ExternalNodeViewModel
import to.bitkit.ui.screens.transfer.external.ExternalSuccessScreen
import to.bitkit.ui.screens.transfer.external.LnurlChannelScreen
-import to.bitkit.ui.screens.wallets.HomeNav
+import to.bitkit.ui.screens.wallets.HomeScreen
+import to.bitkit.ui.screens.wallets.SavingsWalletScreen
+import to.bitkit.ui.screens.wallets.SpendingWalletScreen
import to.bitkit.ui.screens.wallets.activity.ActivityDetailScreen
import to.bitkit.ui.screens.wallets.activity.ActivityExploreScreen
import to.bitkit.ui.screens.wallets.activity.AllActivityScreen
@@ -166,6 +171,7 @@ import to.bitkit.ui.sheets.SendSheet
import to.bitkit.ui.sheets.UpdateSheet
import to.bitkit.ui.theme.TRANSITION_SHEET_MS
import to.bitkit.ui.utils.AutoReadClipboardHandler
+import to.bitkit.ui.utils.RequestNotificationPermissions
import to.bitkit.ui.utils.Transitions
import to.bitkit.ui.utils.composableWithDefaultTransitions
import to.bitkit.ui.utils.navigationWithDefaultTransitions
@@ -195,6 +201,7 @@ fun ContentView(
modifier: Modifier = Modifier,
) {
val navController = rememberNavController()
+
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val context = LocalContext.current
val lifecycle = LocalLifecycleOwner.current.lifecycle
@@ -494,7 +501,7 @@ private fun RootNavHost(
activityListViewModel = activityListViewModel,
settingsViewModel = settingsViewModel,
navController = navController,
- drawerState = drawerState
+ drawerState = drawerState,
)
allActivity(
activityListViewModel = activityListViewModel,
@@ -541,6 +548,7 @@ private fun RootNavHost(
navController.navigateToTransferFunding()
settingsViewModel.setHasSeenTransferIntro(true)
},
+ onBackClick = {},
)
}
composableWithDefaultTransitions {
@@ -630,7 +638,9 @@ private fun RootNavHost(
composableWithDefaultTransitions {
SettingUpScreen(
viewModel = transferViewModel,
- onContinueClick = { navController.popBackStack(inclusive = true) },
+ onContinueClick = {
+ navController.navigateToHome()
+ }
)
}
composableWithDefaultTransitions {
@@ -744,13 +754,77 @@ private fun NavGraphBuilder.home(
drawerState: DrawerState,
) {
composable {
- HomeNav(
- walletViewModel = walletViewModel,
- appViewModel = appViewModel,
- activityListViewModel = activityListViewModel,
- settingsViewModel = settingsViewModel,
- rootNavController = navController,
- drawerState = drawerState,
+ val uiState by walletViewModel.uiState.collectAsStateWithLifecycle()
+ val hazeState = rememberHazeState()
+
+ RequestNotificationPermissions(
+ onPermissionChange = { granted ->
+ settingsViewModel.setNotificationPreference(granted)
+ }
+ )
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .hazeSource(hazeState)
+ ) {
+ HomeScreen(
+ mainUiState = uiState,
+ drawerState = drawerState,
+ rootNavController = navController,
+ walletNavController = navController,
+ settingsViewModel = settingsViewModel,
+ walletViewModel = walletViewModel,
+ appViewModel = appViewModel,
+ activityListViewModel = activityListViewModel,
+ )
+ }
+ }
+ composable(
+ enterTransition = { Transitions.slideInHorizontally },
+ exitTransition = { Transitions.slideOutHorizontally },
+ ) {
+ val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle()
+ val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle()
+ val onchainActivities by activityListViewModel.onchainActivities.collectAsStateWithLifecycle()
+
+ SavingsWalletScreen(
+ isGeoBlocked = isGeoBlocked,
+ onchainActivities = onchainActivities.orEmpty(),
+ onAllActivityButtonClick = { navController.navigateToAllActivity() },
+ onActivityItemClick = { navController.navigateToActivityItem(it) },
+ onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive) },
+ onTransferToSpendingClick = {
+ if (!hasSeenSpendingIntro) {
+ navController.navigateToTransferSpendingIntro()
+ } else {
+ navController.navigateToTransferSpendingAmount()
+ }
+ },
+ onBackClick = { navController.popBackStack() },
+ )
+ }
+ composable(
+ enterTransition = { Transitions.slideInHorizontally },
+ exitTransition = { Transitions.slideOutHorizontally },
+ ) {
+ val hasSeenSavingsIntro by settingsViewModel.hasSeenSavingsIntro.collectAsStateWithLifecycle()
+ val uiState by walletViewModel.uiState.collectAsStateWithLifecycle()
+ val lightningActivities by activityListViewModel.lightningActivities.collectAsStateWithLifecycle()
+
+ SpendingWalletScreen(
+ uiState = uiState,
+ lightningActivities = lightningActivities.orEmpty(),
+ onAllActivityButtonClick = { navController.navigateToAllActivity() },
+ onActivityItemClick = { navController.navigateToActivityItem(it) },
+ onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive) },
+ onTransferToSavingsClick = {
+ if (!hasSeenSavingsIntro) {
+ navController.navigateToTransferSavingsIntro()
+ } else {
+ navController.navigateToTransferSavingsAvailability()
+ }
+ },
+ onBackClick = { navController.popBackStack() },
)
}
}
@@ -818,7 +892,8 @@ private fun NavGraphBuilder.profile(
onContinue = {
settingsViewModel.setHasSeenProfileIntro(true)
navController.navigate(Routes.CreateProfile)
- }
+ },
+ onBackClick = { navController.popBackStack() }
)
}
composableWithDefaultTransitions {
@@ -838,6 +913,9 @@ private fun NavGraphBuilder.shop(
onContinue = {
settingsViewModel.setHasSeenShopIntro(true)
navController.navigate(Routes.ShopDiscover)
+ },
+ onBackClick = {
+ navController.popBackStack()
}
)
}
@@ -1227,7 +1305,8 @@ private fun NavGraphBuilder.widgets(
onContinue = {
settingsViewModel.setHasSeenWidgetsIntro(true)
navController.navigate(Routes.AddWidget)
- }
+ },
+ onBackClick = {},
)
}
composableWithDefaultTransitions {
@@ -1243,6 +1322,7 @@ private fun NavGraphBuilder.widgets(
}
},
fiatSymbol = LocalCurrencies.current.currencySymbol,
+ onBackCLick = { navController.popBackStack() }
)
}
composableWithDefaultTransitions {
@@ -1409,10 +1489,6 @@ inline fun NavController.navigateIfNotCurrent(route: T) {
}
}
-fun NavController.navigateToSettings() = navigate(
- route = Routes.Settings,
-)
-
fun NavController.navigateToGeneralSettings() = navigate(
route = Routes.GeneralSettings,
)
@@ -1557,10 +1633,17 @@ fun NavController.navigateToAboutSettings() = navigate(
)
// endregion
+@Stable
sealed interface Routes {
@Serializable
data object Home : Routes
+ @Serializable
+ data object Savings : Routes
+
+ @Serializable
+ data object Spending : Routes
+
@Serializable
data object Settings : Routes
diff --git a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt
index 8fae03b0f..17c594fbe 100644
--- a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt
@@ -30,7 +30,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.BalanceDetails
import org.lightningdevkit.ldknode.BalanceSource
import org.lightningdevkit.ldknode.BestBlock
@@ -62,12 +61,14 @@ import to.bitkit.ui.components.settings.SettingsTextButtonRow
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.copyToClipboard
import to.bitkit.ui.utils.withAccent
import to.bitkit.viewmodels.MainUiState
+import kotlin.time.Clock.System.now
+import kotlin.time.ExperimentalTime
@Composable
fun NodeInfoScreen(
@@ -463,11 +464,12 @@ private fun Preview() {
}
}
+@OptIn(ExperimentalTime::class)
@Preview(showSystemUi = true)
@Composable
private fun PreviewDevMode() {
AppThemeSurface {
- val syncTime = Clock.System.now().epochSeconds.toULong()
+ val syncTime = now().epochSeconds.toULong()
Content(
isDevModeEnabled = true,
uiState = MainUiState(
diff --git a/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt
index 70d2d330f..00d35fbee 100644
--- a/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt
+++ b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt
@@ -35,7 +35,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.R
import to.bitkit.models.ActivityBannerType
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.outerGlow
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/AppStatus.kt b/app/src/main/java/to/bitkit/ui/components/AppStatus.kt
index 15452402a..9e5ada2d5 100644
--- a/app/src/main/java/to/bitkit/ui/components/AppStatus.kt
+++ b/app/src/main/java/to/bitkit/ui/components/AppStatus.kt
@@ -29,7 +29,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.models.HealthState
import to.bitkit.ui.appViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt b/app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt
index a788a44fb..848ae94fa 100644
--- a/app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt
+++ b/app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt
@@ -31,8 +31,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.env.Env
import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.blockPointerInputPassthrough
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.rememberBiometricAuthSupported
diff --git a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
index 1cb9905a4..0039fff56 100644
--- a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
+++ b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
@@ -29,8 +29,8 @@ import to.bitkit.ui.currencyViewModel
import to.bitkit.ui.settingsViewModel
import to.bitkit.ui.shared.UiConstants
import to.bitkit.ui.shared.animations.BalanceAnimations
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.swipeToHide
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/BiometricsView.kt b/app/src/main/java/to/bitkit/ui/components/BiometricsView.kt
index 8211643d4..d9ddebe48 100644
--- a/app/src/main/java/to/bitkit/ui/components/BiometricsView.kt
+++ b/app/src/main/java/to/bitkit/ui/components/BiometricsView.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import to.bitkit.R
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.utils.BiometricPrompt
diff --git a/app/src/main/java/to/bitkit/ui/components/BottomSheet.kt b/app/src/main/java/to/bitkit/ui/components/BottomSheet.kt
index 1ea07354b..eaf26b420 100644
--- a/app/src/main/java/to/bitkit/ui/components/BottomSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/components/BottomSheet.kt
@@ -23,7 +23,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.tooling.preview.Preview
@@ -86,14 +85,14 @@ fun BottomSheetPreview(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
- val density = LocalDensity.current
BottomSheet(
onDismissRequest = {},
sheetState = remember {
SheetState(
skipPartiallyExpanded = true,
- density = density,
initialValue = SheetValue.Expanded,
+ positionalThreshold = { 0f },
+ velocityThreshold = { 0f },
)
},
modifier = modifier,
diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt
index f18aecdc4..6da684202 100644
--- a/app/src/main/java/to/bitkit/ui/components/Button.kt
+++ b/app/src/main/java/to/bitkit/ui/components/Button.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import to.bitkit.ui.shared.modifiers.alphaFeedback
import to.bitkit.ui.shared.util.primaryButtonStyle
import to.bitkit.ui.theme.AppButtonDefaults
import to.bitkit.ui.theme.AppThemeSurface
@@ -72,57 +73,49 @@ fun PrimaryButton(
containerColor = Color.Transparent,
disabledContainerColor = Color.Transparent
),
- contentPadding = PaddingValues(0.dp),
+ contentPadding = contentPadding,
shape = buttonShape,
- modifier = Modifier
+ modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
- .then(modifier)
+ .primaryButtonStyle(
+ isEnabled = enabled && !isLoading,
+ shape = buttonShape,
+ primaryColor = color
+ )
+ .alphaFeedback(enabled = enabled && !isLoading)
) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier
- .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
- .requiredHeight(size.height)
- .primaryButtonStyle(
- isEnabled = enabled && !isLoading,
- shape = buttonShape,
- primaryColor = color
- )
- .padding(contentPadding)
- ) {
- if (isLoading) {
- CircularProgressIndicator(
- color = Colors.White32,
- strokeWidth = 2.dp,
- modifier = Modifier.size(size.height / 2)
- )
- } else {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- if (icon != null) {
- Box(
- modifier = if (enabled) {
- Modifier
- } else {
- Modifier.graphicsLayer {
- colorFilter = ColorFilter.tint(Colors.White32)
- }
+ if (isLoading) {
+ CircularProgressIndicator(
+ color = Colors.White32,
+ strokeWidth = 2.dp,
+ modifier = Modifier.size(size.height / 2)
+ )
+ } else {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ if (icon != null) {
+ Box(
+ modifier = if (enabled) {
+ Modifier
+ } else {
+ Modifier.graphicsLayer {
+ colorFilter = ColorFilter.tint(Colors.White32)
}
- ) {
- icon()
}
- }
- text?.let {
- Text(
- text = text,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- )
+ ) {
+ icon()
}
}
+ text?.let {
+ Text(
+ text = text,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
}
}
}
@@ -147,10 +140,9 @@ fun SecondaryButton(
colors = AppButtonDefaults.secondaryColors,
contentPadding = contentPadding,
border = border,
- modifier = Modifier
+ modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
- .then(modifier)
) {
if (isLoading) {
CircularProgressIndicator(
@@ -205,10 +197,9 @@ fun TertiaryButton(
enabled = enabled && !isLoading,
colors = AppButtonDefaults.tertiaryColors,
contentPadding = contentPadding,
- modifier = Modifier
+ modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
- .then(modifier)
) {
if (isLoading) {
CircularProgressIndicator(
@@ -258,6 +249,11 @@ private fun PrimaryButtonPreview() {
text = "Primary",
onClick = {},
)
+ PrimaryButton(
+ text = "Primary with padding",
+ modifier = Modifier.padding(horizontal = 32.dp),
+ onClick = {},
+ )
PrimaryButton(
text = "Primary With Icon",
onClick = {},
@@ -292,6 +288,25 @@ private fun PrimaryButtonPreview() {
size = ButtonSize.Small,
onClick = {},
)
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PrimaryButton(
+ text = "Primary Small",
+ fullWidth = false,
+ size = ButtonSize.Small,
+ modifier = Modifier.weight(1f),
+ onClick = {},
+ )
+ PrimaryButton(
+ text = "Primary Small",
+ fullWidth = false,
+ size = ButtonSize.Small,
+ modifier = Modifier.weight(1f),
+ onClick = {},
+ )
+ }
PrimaryButton(
text = "Primary Small Color Not Full",
size = ButtonSize.Small,
@@ -360,6 +375,11 @@ private fun SecondaryButtonPreview() {
text = "Secondary",
onClick = {},
)
+ SecondaryButton(
+ text = "Secondary With padding",
+ modifier = Modifier.padding(horizontal = 32.dp),
+ onClick = {},
+ )
SecondaryButton(
text = "Secondary With Icon",
onClick = {},
diff --git a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt
index 7c3c31822..0071996ac 100644
--- a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt
+++ b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt
@@ -46,8 +46,8 @@ import to.bitkit.R
import to.bitkit.ui.Routes
import to.bitkit.ui.navigateIfNotCurrent
import to.bitkit.ui.navigateToHome
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.blockPointerInputPassthrough
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.theme.InterFontFamily
@@ -138,11 +138,8 @@ private fun Menu(
label = stringResource(R.string.wallet__drawer__wallet),
iconRes = R.drawable.ic_coins,
onClick = {
- val isOnHome = rootNavController.currentBackStackEntry
- ?.destination?.hasRoute() ?: false
- if (!isOnHome) {
- rootNavController.navigateToHome()
- }
+ val isInHome = rootNavController.currentBackStackEntry?.destination?.hasRoute() ?: false
+ if (!isInHome) rootNavController.navigateToHome()
scope.launch { drawerState.close() }
},
modifier = Modifier.testTag("DrawerWallet")
diff --git a/app/src/main/java/to/bitkit/ui/components/Money.kt b/app/src/main/java/to/bitkit/ui/components/Money.kt
index 899e377b8..cc863d7a1 100644
--- a/app/src/main/java/to/bitkit/ui/components/Money.kt
+++ b/app/src/main/java/to/bitkit/ui/components/Money.kt
@@ -16,7 +16,7 @@ import to.bitkit.models.formatToModernDisplay
import to.bitkit.repositories.CurrencyState
import to.bitkit.ui.LocalCurrencies
import to.bitkit.ui.currencyViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.withAccent
import java.math.BigDecimal
diff --git a/app/src/main/java/to/bitkit/ui/components/NumberPad.kt b/app/src/main/java/to/bitkit/ui/components/NumberPad.kt
index 636e47df5..fba65ef8e 100644
--- a/app/src/main/java/to/bitkit/ui/components/NumberPad.kt
+++ b/app/src/main/java/to/bitkit/ui/components/NumberPad.kt
@@ -33,7 +33,7 @@ import to.bitkit.models.PrimaryDisplay
import to.bitkit.repositories.CurrencyState
import to.bitkit.ui.LocalCurrencies
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.viewmodels.AmountInputViewModel
diff --git a/app/src/main/java/to/bitkit/ui/components/NumberPadActionButton.kt b/app/src/main/java/to/bitkit/ui/components/NumberPadActionButton.kt
index 9fde35af7..f0691860b 100644
--- a/app/src/main/java/to/bitkit/ui/components/NumberPadActionButton.kt
+++ b/app/src/main/java/to/bitkit/ui/components/NumberPadActionButton.kt
@@ -2,15 +2,17 @@ package to.bitkit.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
import androidx.compose.material3.Icon
-import androidx.compose.material3.Surface
+import androidx.compose.material3.OutlinedButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -19,7 +21,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.R
-import to.bitkit.ui.theme.AppShapes
+import to.bitkit.ui.shared.modifiers.alphaFeedback
+import to.bitkit.ui.shared.util.primaryButtonStyle
+import to.bitkit.ui.theme.AppButtonDefaults
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
@@ -32,36 +36,72 @@ fun NumberPadActionButton(
enabled: Boolean = true,
@DrawableRes icon: Int? = null,
) {
- val borderColor = if (enabled) Color.Transparent else color
- val bgColor = if (enabled) Colors.White10 else Color.Transparent
+ val contentPadding = PaddingValues(horizontal = 8.dp, vertical = 5.dp)
+ val height = 28.dp
+ val buttonShape = RoundedCornerShape(8.dp)
- Surface(
- color = bgColor,
- shape = AppShapes.small,
- border = BorderStroke(1.dp, borderColor),
- modifier = modifier
- .clickable(
- enabled = enabled,
- onClick = onClick,
- )
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- modifier = Modifier.padding(PaddingValues(8.dp, 5.dp))
+ if (enabled) {
+ Button(
+ onClick = onClick,
+ colors = AppButtonDefaults.primaryColors.copy(
+ containerColor = Color.Transparent,
+ disabledContainerColor = Color.Transparent
+ ),
+ contentPadding = contentPadding,
+ shape = buttonShape,
+ modifier = modifier
+ .requiredHeight(height)
+ .primaryButtonStyle(
+ isEnabled = true,
+ shape = buttonShape,
+ )
+ .alphaFeedback(enabled = enabled)
) {
- if (icon != null) {
- Icon(
- painter = painterResource(icon),
- contentDescription = text,
- tint = color,
- modifier = Modifier.size(16.dp)
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ if (icon != null) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = text,
+ tint = color,
+ modifier = Modifier.size(16.dp)
+ )
+ }
+ Caption13Up(
+ text = text,
+ color = color,
+ )
+ }
+ }
+ } else {
+ OutlinedButton(
+ onClick = onClick,
+ shape = buttonShape,
+ colors = AppButtonDefaults.secondaryColors,
+ contentPadding = contentPadding,
+ border = BorderStroke(width = 1.dp, color = color),
+ modifier = modifier
+ .requiredHeight(height)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ if (icon != null) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = text,
+ tint = color,
+ modifier = Modifier.size(16.dp)
+ )
+ }
+ Caption13Up(
+ text = text,
+ color = color,
)
}
- Caption13Up(
- text = text,
- color = color,
- )
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt b/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
index 7f1cb0257..add65817c 100644
--- a/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
+++ b/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
@@ -22,7 +22,7 @@ import to.bitkit.models.USD_SYMBOL
import to.bitkit.models.formatToModernDisplay
import to.bitkit.repositories.CurrencyState
import to.bitkit.ui.LocalCurrencies
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.viewmodels.AmountInputUiState
diff --git a/app/src/main/java/to/bitkit/ui/components/SearchInput.kt b/app/src/main/java/to/bitkit/ui/components/SearchInput.kt
index de619e885..8e3672eab 100644
--- a/app/src/main/java/to/bitkit/ui/components/SearchInput.kt
+++ b/app/src/main/java/to/bitkit/ui/components/SearchInput.kt
@@ -27,7 +27,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import to.bitkit.R
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppTextFieldDefaults
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt b/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt
index 92aea1ddc..8c1a36fb8 100644
--- a/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt
+++ b/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt
@@ -38,7 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.R
import to.bitkit.models.Suggestion
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/TabBar.kt b/app/src/main/java/to/bitkit/ui/components/TabBar.kt
index 0a1de06ba..fd629f34e 100644
--- a/app/src/main/java/to/bitkit/ui/components/TabBar.kt
+++ b/app/src/main/java/to/bitkit/ui/components/TabBar.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
@@ -40,7 +41,7 @@ import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.rememberHazeState
import to.bitkit.R
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.shared.util.primaryButtonStyle
import to.bitkit.ui.theme.AppThemeSurface
@@ -175,7 +176,7 @@ fun BoxScope.TabBar(
radius = (size.width - borderWidth) / 2,
center = Offset(size.width / 2, size.height / 2),
style = Stroke(width = borderWidth),
- blendMode = androidx.compose.ui.graphics.BlendMode.DstIn
+ blendMode = BlendMode.DstIn
)
}
.clickableAlpha { onScanClick() }
diff --git a/app/src/main/java/to/bitkit/ui/components/Tag.kt b/app/src/main/java/to/bitkit/ui/components/Tag.kt
index 50c12db3a..cf6784141 100644
--- a/app/src/main/java/to/bitkit/ui/components/Tag.kt
+++ b/app/src/main/java/to/bitkit/ui/components/Tag.kt
@@ -18,7 +18,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.R
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppShapes
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/Tooltip.kt b/app/src/main/java/to/bitkit/ui/components/Tooltip.kt
index dfd0425d8..49547f262 100644
--- a/app/src/main/java/to/bitkit/ui/components/Tooltip.kt
+++ b/app/src/main/java/to/bitkit/ui/components/Tooltip.kt
@@ -4,14 +4,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.PlainTooltip
+import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
+import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider
import androidx.compose.material3.TooltipState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.PopupPositionProvider
import to.bitkit.ui.theme.Colors
@OptIn(ExperimentalMaterial3Api::class)
@@ -20,16 +23,20 @@ fun Tooltip(
text: String,
tooltipState: TooltipState,
modifier: Modifier = Modifier,
- content: @Composable () -> Unit
+ positionProvider: PopupPositionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above, 4.dp),
+ focusable: Boolean = false,
+ content: @Composable () -> Unit,
) {
TooltipBox(
modifier = modifier,
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = positionProvider,
tooltip = {
PlainTooltip(
- caretSize = DpSize(
- width = 16.dp,
- height = 12.dp
+ caretShape = TooltipDefaults.caretShape(
+ DpSize(
+ width = 16.dp,
+ height = 12.dp
+ )
),
containerColor = Colors.Black92,
contentColor = Colors.White,
@@ -48,7 +55,7 @@ fun Tooltip(
}
},
state = tooltipState,
- focusable = false,
- content = content
+ focusable = focusable,
+ content = content,
)
}
diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt
index ae6cbd5d7..cf2abc86a 100644
--- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt
+++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt
@@ -30,7 +30,7 @@ import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyMSB
import to.bitkit.ui.components.BodyS
import to.bitkit.ui.components.BodySSB
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt
index ed1946ca9..194057284 100644
--- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt
+++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.ui.components.BodyM
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppSwitchDefaults
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsTextButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsTextButtonRow.kt
index 67a80285f..7eb66a4e6 100644
--- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsTextButtonRow.kt
+++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsTextButtonRow.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.unit.dp
import to.bitkit.R
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.Caption
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.screen
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/onboarding/RestoreWalletScreen.kt b/app/src/main/java/to/bitkit/ui/onboarding/RestoreWalletScreen.kt
index b30b5183c..aece81cf7 100644
--- a/app/src/main/java/to/bitkit/ui/onboarding/RestoreWalletScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/onboarding/RestoreWalletScreen.kt
@@ -53,7 +53,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/ProfileIntroScreen.kt b/app/src/main/java/to/bitkit/ui/screens/profile/ProfileIntroScreen.kt
index 03126789c..27fe39a66 100644
--- a/app/src/main/java/to/bitkit/ui/screens/profile/ProfileIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/profile/ProfileIntroScreen.kt
@@ -26,11 +26,12 @@ import to.bitkit.ui.utils.withAccent
@Composable
fun ProfileIntroScreen(
onContinue: () -> Unit,
+ onBackClick: () -> Unit,
) {
ScreenColumn {
AppTopBar(
titleText = stringResource(R.string.slashtags__profile),
- onBackClick = null,
+ onBackClick = onBackClick,
actions = { DrawerNavIcon() },
)
@@ -68,7 +69,8 @@ fun ProfileIntroScreen(
private fun Preview() {
AppThemeSurface {
ProfileIntroScreen(
- onContinue = {}
+ onContinue = {},
+ onBackClick = {}
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryMnemonicScreen.kt b/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryMnemonicScreen.kt
index a28860e06..47282b6b5 100644
--- a/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryMnemonicScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryMnemonicScreen.kt
@@ -20,7 +20,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import to.bitkit.R
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.FillHeight
diff --git a/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryModeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryModeScreen.kt
index 77de8c66b..4051a6fdc 100644
--- a/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryModeScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/recovery/RecoveryModeScreen.kt
@@ -17,7 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import to.bitkit.R
import to.bitkit.ui.components.AuthCheckView
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/screens/settings/DevSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/screens/settings/DevSettingsScreen.kt
index 41aed839a..455136253 100644
--- a/app/src/main/java/to/bitkit/ui/screens/settings/DevSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/settings/DevSettingsScreen.kt
@@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import org.lightningdevkit.ldknode.Network
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/screens/settings/FeeSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/screens/settings/FeeSettingsScreen.kt
index d7fee8bc0..34cb37e89 100644
--- a/app/src/main/java/to/bitkit/ui/screens/settings/FeeSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/settings/FeeSettingsScreen.kt
@@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.synonym.bitkitcore.FeeRates
diff --git a/app/src/main/java/to/bitkit/ui/screens/shop/ShopIntroScreen.kt b/app/src/main/java/to/bitkit/ui/screens/shop/ShopIntroScreen.kt
index 269865df5..17fa1ea7e 100644
--- a/app/src/main/java/to/bitkit/ui/screens/shop/ShopIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/shop/ShopIntroScreen.kt
@@ -26,11 +26,12 @@ import to.bitkit.ui.utils.withAccent
@Composable
fun ShopIntroScreen(
onContinue: () -> Unit,
+ onBackClick: () -> Unit,
) {
ScreenColumn {
AppTopBar(
titleText = "",
- onBackClick = null,
+ onBackClick = onBackClick,
actions = { DrawerNavIcon() },
)
@@ -66,7 +67,8 @@ fun ShopIntroScreen(
private fun Preview() {
AppThemeSurface {
ShopIntroScreen(
- onContinue = {}
+ onContinue = {},
+ onBackClick = {},
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt
index 4184c6c05..8517a76eb 100644
--- a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt
@@ -49,7 +49,7 @@ import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAdvancedScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAdvancedScreen.kt
index 8b4af25e7..7e92fad64 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAdvancedScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAdvancedScreen.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ext.mockOrder
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAmountScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAmountScreen.kt
index ec32369c8..d313421c2 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAmountScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAmountScreen.kt
@@ -18,7 +18,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.repositories.CurrencyState
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt
index 0432775b9..dd2c4cc60 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt
@@ -27,7 +27,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.synonym.bitkitcore.BtBolt11InvoiceState
import com.synonym.bitkitcore.BtOrderState
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/TransferIntroScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/TransferIntroScreen.kt
index 2c0c514f0..45192970b 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/TransferIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/TransferIntroScreen.kt
@@ -33,6 +33,7 @@ import to.bitkit.ui.utils.withAccent
@Composable
fun TransferIntroScreen(
onContinueClick: () -> Unit = {},
+ onBackClick: () -> Unit,
) {
Box(
contentAlignment = Alignment.TopCenter,
@@ -51,7 +52,7 @@ fun TransferIntroScreen(
)
AppTopBar(
titleText = null,
- onBackClick = null,
+ onBackClick = onBackClick,
actions = { DrawerNavIcon() },
)
Column(
@@ -79,6 +80,6 @@ fun TransferIntroScreen(
@Composable
private fun Preview() {
AppThemeSurface {
- TransferIntroScreen()
+ TransferIntroScreen(onBackClick = {})
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalAmountScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalAmountScreen.kt
index cd321f96c..d960b32db 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalAmountScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalAmountScreen.kt
@@ -19,7 +19,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.repositories.CurrencyState
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalConfirmScreen.kt
index 91f514e34..fbaae5a19 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalConfirmScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/external/ExternalConfirmScreen.kt
@@ -41,7 +41,7 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.screens.transfer.external.ExternalNodeContract.SideEffect
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.withAccent
diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/external/LnurlChannelScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/external/LnurlChannelScreen.kt
index 6329212e9..67b985d97 100644
--- a/app/src/main/java/to/bitkit/ui/screens/transfer/external/LnurlChannelScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/transfer/external/LnurlChannelScreen.kt
@@ -20,7 +20,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.env.Env
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeNav.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeNav.kt
deleted file mode 100644
index 74d4507d3..000000000
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeNav.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-package to.bitkit.ui.screens.wallets
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.DrawerState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavController
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import dev.chrisbanes.haze.hazeSource
-import dev.chrisbanes.haze.rememberHazeState
-import kotlinx.serialization.Serializable
-import to.bitkit.ui.components.Sheet
-import to.bitkit.ui.navigateToActivityItem
-import to.bitkit.ui.navigateToAllActivity
-import to.bitkit.ui.navigateToTransferSavingsAvailability
-import to.bitkit.ui.navigateToTransferSavingsIntro
-import to.bitkit.ui.navigateToTransferSpendingAmount
-import to.bitkit.ui.navigateToTransferSpendingIntro
-import to.bitkit.ui.utils.RequestNotificationPermissions
-import to.bitkit.ui.utils.Transitions
-import to.bitkit.viewmodels.ActivityListViewModel
-import to.bitkit.viewmodels.AppViewModel
-import to.bitkit.viewmodels.MainUiState
-import to.bitkit.viewmodels.SettingsViewModel
-import to.bitkit.viewmodels.WalletViewModel
-
-@Composable
-fun HomeNav(
- walletViewModel: WalletViewModel,
- appViewModel: AppViewModel,
- activityListViewModel: ActivityListViewModel,
- settingsViewModel: SettingsViewModel,
- rootNavController: NavController,
- drawerState: DrawerState,
-) {
- val uiState: MainUiState by walletViewModel.uiState.collectAsStateWithLifecycle()
- val hazeState = rememberHazeState()
-
- RequestNotificationPermissions(
- onPermissionChange = { granted ->
- settingsViewModel.setNotificationPreference(granted)
- }
- )
-
- val walletNavController = rememberNavController()
-
- Box(
- modifier = Modifier
- .fillMaxSize()
- .hazeSource(hazeState)
- ) {
- NavContent(
- walletNavController = walletNavController,
- rootNavController = rootNavController,
- mainUiState = uiState,
- drawerState = drawerState,
- settingsViewModel = settingsViewModel,
- appViewModel = appViewModel,
- walletViewModel = walletViewModel,
- activityListViewModel = activityListViewModel,
- )
- }
-}
-
-@Composable
-private fun NavContent(
- walletNavController: NavHostController,
- rootNavController: NavController,
- mainUiState: MainUiState,
- drawerState: DrawerState,
- settingsViewModel: SettingsViewModel,
- appViewModel: AppViewModel,
- walletViewModel: WalletViewModel,
- activityListViewModel: ActivityListViewModel,
-) {
- NavHost(
- navController = walletNavController,
- startDestination = HomeRoutes.Home,
- ) {
- composable {
- HomeScreen(
- mainUiState = mainUiState,
- drawerState = drawerState,
- rootNavController = rootNavController,
- walletNavController = walletNavController,
- settingsViewModel = settingsViewModel,
- walletViewModel = walletViewModel,
- appViewModel = appViewModel,
- activityListViewModel = activityListViewModel,
- )
- }
- composable(
- enterTransition = { Transitions.slideInHorizontally },
- exitTransition = { Transitions.slideOutHorizontally },
- ) {
- val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle()
- val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle()
- val onchainActivities by activityListViewModel.onchainActivities.collectAsStateWithLifecycle()
-
- SavingsWalletScreen(
- isGeoBlocked = isGeoBlocked,
- onchainActivities = onchainActivities.orEmpty(),
- onAllActivityButtonClick = { rootNavController.navigateToAllActivity() },
- onActivityItemClick = { rootNavController.navigateToActivityItem(it) },
- onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive) },
- onTransferToSpendingClick = {
- if (!hasSeenSpendingIntro) {
- rootNavController.navigateToTransferSpendingIntro()
- } else {
- rootNavController.navigateToTransferSpendingAmount()
- }
- },
- onBackClick = { walletNavController.popBackStack() },
- )
- }
- composable(
- enterTransition = { Transitions.slideInHorizontally },
- exitTransition = { Transitions.slideOutHorizontally },
- ) {
- val hasSeenSavingsIntro by settingsViewModel.hasSeenSavingsIntro.collectAsStateWithLifecycle()
- val lightningActivities by activityListViewModel.lightningActivities.collectAsStateWithLifecycle()
- SpendingWalletScreen(
- uiState = mainUiState,
- lightningActivities = lightningActivities.orEmpty(),
- onAllActivityButtonClick = { rootNavController.navigateToAllActivity() },
- onActivityItemClick = { rootNavController.navigateToActivityItem(it) },
- onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive) },
- onTransferToSavingsClick = {
- if (!hasSeenSavingsIntro) {
- rootNavController.navigateToTransferSavingsIntro()
- } else {
- rootNavController.navigateToTransferSavingsAvailability()
- }
- },
- onBackClick = { walletNavController.popBackStack() },
- )
- }
- }
-}
-
-object HomeRoutes {
- @Serializable
- data object Home
-
- @Serializable
- data object Savings
-
- @Serializable
- data object Spending
-}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
index 876883ef2..5e8a6c64d 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
@@ -54,7 +54,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavHostController
@@ -106,7 +106,7 @@ import to.bitkit.ui.screens.widgets.facts.FactsCard
import to.bitkit.ui.screens.widgets.headlines.HeadlineCard
import to.bitkit.ui.screens.widgets.price.PriceCard
import to.bitkit.ui.screens.widgets.weather.WeatherCard
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.shareText
import to.bitkit.ui.sheets.BackupRoute
import to.bitkit.ui.sheets.PinRoute
@@ -359,7 +359,7 @@ private fun Content(
sats = balances.totalOnchainSats.toLong(),
icon = painterResource(id = R.drawable.ic_btc_circle),
modifier = Modifier
- .clickableAlpha { walletNavController.navigate(HomeRoutes.Savings) }
+ .clickableAlpha { walletNavController.navigate(Routes.Savings) }
.padding(vertical = 4.dp)
.testTag("ActivitySavings")
)
@@ -370,7 +370,7 @@ private fun Content(
sats = balances.totalLightningSats.toLong(),
icon = painterResource(id = R.drawable.ic_ln_circle),
modifier = Modifier
- .clickableAlpha { walletNavController.navigate(HomeRoutes.Spending) }
+ .clickableAlpha { walletNavController.navigate(Routes.Spending) }
.padding(vertical = 4.dp)
.testTag("ActivitySpending")
)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt
index 5b0befd82..d071dfe6d 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt
@@ -35,6 +35,7 @@ import to.bitkit.ui.components.IncomingTransfer
import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.components.TabBar
import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped
import to.bitkit.ui.screens.wallets.activity.utils.previewOnchainActivityItems
@@ -74,6 +75,7 @@ fun SavingsWalletScreen(
contentScale = ContentScale.Fit,
modifier = Modifier
.align(Alignment.TopEnd)
+ .padding(top = 32.dp)
.offset(x = (120).dp)
.size(268.dp)
)
@@ -82,6 +84,9 @@ fun SavingsWalletScreen(
titleText = stringResource(R.string.wallet__savings__title),
icon = painterResource(R.drawable.ic_btc_circle),
onBackClick = onBackClick,
+ actions = {
+ DrawerNavIcon()
+ }
)
Column(
modifier = Modifier.padding(horizontal = 16.dp)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt
index d94c9289d..82460dca1 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt
@@ -36,6 +36,7 @@ import to.bitkit.ui.components.IncomingTransfer
import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.components.TabBar
import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped
import to.bitkit.ui.screens.wallets.activity.utils.previewLightningActivityItems
@@ -77,7 +78,7 @@ fun SpendingWalletScreen(
contentScale = ContentScale.Fit,
modifier = Modifier
.align(Alignment.TopEnd)
- .offset(x = (155).dp, y = (-35).dp)
+ .offset(x = (155).dp)
.size(330.dp)
)
ScreenColumn(noBackground = true) {
@@ -85,6 +86,9 @@ fun SpendingWalletScreen(
titleText = stringResource(R.string.wallet__spending__title),
icon = painterResource(R.drawable.ic_ln_circle),
onBackClick = onBackClick,
+ actions = {
+ DrawerNavIcon()
+ }
)
Column(
modifier = Modifier.padding(horizontal = 16.dp)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt
index 7a737600e..ec593c539 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt
@@ -39,7 +39,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.synonym.bitkitcore.Activity
import com.synonym.bitkitcore.LightningActivity
@@ -47,6 +47,7 @@ import com.synonym.bitkitcore.OnchainActivity
import com.synonym.bitkitcore.PaymentState
import com.synonym.bitkitcore.PaymentType
import to.bitkit.R
+import to.bitkit.ext.create
import to.bitkit.ext.ellipsisMiddle
import to.bitkit.ext.isSent
import to.bitkit.ext.isTransfer
@@ -70,8 +71,8 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.screens.wallets.activity.components.ActivityAddTagSheet
import to.bitkit.ui.screens.wallets.activity.components.ActivityIcon
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.sheets.BoostTransactionSheet
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
@@ -824,18 +825,15 @@ private fun PreviewLightningSent() {
AppThemeSurface {
ActivityDetailContent(
item = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-1",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 50000UL,
- fee = 1UL,
invoice = "lnbc...",
- message = "Thanks for paying at the bar. Here's my share.",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- preimage = null,
- createdAt = null,
- updatedAt = null,
+ fee = 1UL,
+ message = "Thanks for paying at the bar. Here's my share.",
)
),
tags = listOf("Lunch", "Drinks"),
@@ -855,25 +853,17 @@ private fun PreviewOnchain() {
AppThemeSurface {
ActivityDetailContent(
item = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-1",
txType = PaymentType.RECEIVED,
txId = "abc123",
value = 100000UL,
fee = 500UL,
- feeRate = 8UL,
address = "bc1...",
- confirmed = true,
timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(),
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
+ confirmed = true,
+ feeRate = 8UL,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
)
),
tags = emptyList(),
@@ -896,18 +886,15 @@ private fun PreviewSheetSmallScreen() {
) {
ActivityDetailContent(
item = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-1",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 50000UL,
- fee = 1UL,
invoice = "lnbc...",
- message = "Thanks for paying at the bar. Here's my share.",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- preimage = null,
- createdAt = null,
- updatedAt = null,
+ fee = 1UL,
+ message = "Thanks for paying at the bar. Here's my share.",
)
),
tags = listOf("Lunch", "Drinks"),
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt
index 4ceb770f3..fd7cdc3f6 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt
@@ -32,7 +32,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.synonym.bitkitcore.Activity
import com.synonym.bitkitcore.LightningActivity
@@ -41,6 +41,7 @@ import com.synonym.bitkitcore.PaymentState
import com.synonym.bitkitcore.PaymentType
import org.lightningdevkit.ldknode.TransactionDetails
import to.bitkit.R
+import to.bitkit.ext.create
import to.bitkit.ext.ellipsisMiddle
import to.bitkit.ext.isSent
import to.bitkit.ext.totalValue
@@ -55,7 +56,7 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.screens.wallets.activity.components.ActivityIcon
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.copyToClipboard
@@ -386,18 +387,16 @@ private fun PreviewLightning() {
AppThemeSurface {
ActivityExploreContent(
item = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-1",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 50000UL,
- fee = 1UL,
invoice = "lnbc...",
- message = "Thanks for paying at the bar. Here's my share.",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
+ fee = 1UL,
+ message = "Thanks for paying at the bar. Here's my share.",
preimage = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
- createdAt = null,
- updatedAt = null,
),
),
)
@@ -410,25 +409,17 @@ private fun PreviewOnchain() {
AppThemeSurface {
ActivityExploreContent(
item = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-1",
txType = PaymentType.RECEIVED,
txId = "abc123",
value = 100000UL,
fee = 500UL,
- feeRate = 8UL,
address = "bc1...",
- confirmed = true,
timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(),
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
+ confirmed = true,
+ feeRate = 8UL,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
),
),
)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt
index 757f6c9c7..6c47947fa 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalTime::class)
+
package to.bitkit.ui.screens.wallets.activity
import androidx.activity.compose.BackHandler
@@ -47,11 +49,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
-import kotlinx.datetime.Clock
-import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atStartOfDayIn
+import kotlinx.datetime.number
import kotlinx.datetime.toLocalDateTime
import to.bitkit.R
import to.bitkit.ext.CalendarConstants
@@ -73,8 +74,8 @@ import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.components.SheetSize
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
@@ -83,7 +84,10 @@ import java.util.Calendar
import java.util.Locale
import kotlin.math.abs
import kotlin.math.roundToInt
+import kotlin.time.Clock.System.now
import kotlin.time.Duration.Companion.days
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
// Animation
private const val SWIPE_THRESHOLD_DP = 100
@@ -145,7 +149,7 @@ private fun Content(
Instant.fromEpochMilliseconds(it)
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
- } ?: Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
+ } ?: now().toLocalDateTime(TimeZone.currentSystemDefault()).date
)
}
@@ -476,7 +480,7 @@ private fun CalendarGrid(
}
val today = remember {
- Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
+ now().toLocalDateTime(TimeZone.currentSystemDefault()).date
}
Column(
@@ -532,7 +536,7 @@ private fun CalendarDayView(
modifier = modifier
.aspectRatio(DAY_ASPECT_RATIO)
.clickableAlpha(onClick = onClick)
- .testTag(if (isToday) "Today" else "Day-${date.dayOfMonth}")
+ .testTag(if (isToday) "Today" else "Day-${date.day}")
) {
// Background for range selection
if (isSelected) {
@@ -616,7 +620,7 @@ private fun CalendarDayView(
// Day number
BodyMSB(
- text = date.dayOfMonth.toString(),
+ text = date.day.toString(),
color = if (isStartDate || isEndDate) Colors.Brand else Color.White
)
}
@@ -625,7 +629,7 @@ private fun CalendarDayView(
private fun LocalDate.toFormattedString(): String {
val formatter = SimpleDateFormat(DatePattern.DATE_FORMAT, Locale.getDefault())
val calendar = Calendar.getInstance()
- calendar.set(year, monthNumber - CalendarConstants.MONTH_INDEX_OFFSET, dayOfMonth)
+ calendar.set(year, month.number - CalendarConstants.MONTH_INDEX_OFFSET, day)
return formatter.format(calendar.time)
}
@@ -645,10 +649,10 @@ private fun PreviewWithSelection() {
AppThemeSurface {
BottomSheetPreview {
Content(
- initialStartDate = Clock.System.now()
+ initialStartDate = now()
.minus(CalendarConstants.DAYS_IN_WEEK.days)
.toEpochMilliseconds(),
- initialEndDate = Clock.System.now().toEpochMilliseconds(),
+ initialEndDate = now().toEpochMilliseconds(),
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt
index f2809ab6a..c2d852389 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt
@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.tooling.preview.Preview
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.ui.components.BottomSheet
import to.bitkit.ui.components.BottomSheetPreview
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt
index dc677fc01..ddeed3e91 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt
@@ -24,6 +24,7 @@ import com.synonym.bitkitcore.OnchainActivity
import com.synonym.bitkitcore.PaymentState
import com.synonym.bitkitcore.PaymentType
import to.bitkit.R
+import to.bitkit.ext.create
import to.bitkit.ext.doesExist
import to.bitkit.ext.isBoosting
import to.bitkit.ext.isTransfer
@@ -131,8 +132,8 @@ fun CircularIcon(
icon: Painter,
iconColor: Color,
backgroundColor: Color,
- size: Dp = 32.dp,
modifier: Modifier = Modifier,
+ size: Dp = 32.dp,
) {
Box(
contentAlignment = Alignment.Center,
@@ -160,18 +161,14 @@ private fun Preview() {
// Lightning Sent Succeeded
ActivityIcon(
activity = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-1",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 50000uL,
- fee = 1uL,
invoice = "lnbc...",
- message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- preimage = null,
- createdAt = null,
- updatedAt = null,
+ fee = 1uL,
)
)
)
@@ -179,18 +176,14 @@ private fun Preview() {
// Lightning Received Failed
ActivityIcon(
activity = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-2",
txType = PaymentType.RECEIVED,
status = PaymentState.FAILED,
value = 50000uL,
- fee = 1uL,
invoice = "lnbc...",
- message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- preimage = null,
- createdAt = null,
- updatedAt = null,
+ fee = 1uL,
)
)
)
@@ -198,18 +191,14 @@ private fun Preview() {
// Lightning Pending
ActivityIcon(
activity = Activity.Lightning(
- v1 = LightningActivity(
+ v1 = LightningActivity.create(
id = "test-lightning-3",
txType = PaymentType.SENT,
status = PaymentState.PENDING,
value = 50000uL,
- fee = 1uL,
invoice = "lnbc...",
- message = "",
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- preimage = null,
- createdAt = null,
- updatedAt = null,
+ fee = 1uL,
)
)
)
@@ -217,25 +206,17 @@ private fun Preview() {
// Onchain Received
ActivityIcon(
activity = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-1",
txType = PaymentType.RECEIVED,
txId = "abc123",
value = 100000uL,
fee = 500uL,
- feeRate = 8uL,
address = "bc1...",
- confirmed = true,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
+ confirmed = true,
+ feeRate = 8uL,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
)
)
)
@@ -243,25 +224,17 @@ private fun Preview() {
// Onchain BOOST CPFP
ActivityIcon(
activity = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-1",
txType = PaymentType.RECEIVED,
txId = "abc123",
value = 100000uL,
fee = 500uL,
- feeRate = 8uL,
address = "bc1...",
- confirmed = false,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
+ feeRate = 8uL,
isBoosted = true,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
)
)
)
@@ -269,25 +242,17 @@ private fun Preview() {
// Onchain BOOST RBF
ActivityIcon(
activity = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-1",
txType = PaymentType.SENT,
txId = "abc123",
value = 100000uL,
fee = 500uL,
- feeRate = 8uL,
address = "bc1...",
- confirmed = false,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
+ feeRate = 8uL,
isBoosted = true,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
)
)
)
@@ -295,25 +260,19 @@ private fun Preview() {
// Onchain Transfer
ActivityIcon(
activity = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-2",
txType = PaymentType.SENT,
txId = "abc123",
value = 100000uL,
fee = 500uL,
- feeRate = 8uL,
address = "bc1...",
- confirmed = true,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- isBoosted = false,
- boostTxIds = emptyList(),
+ confirmed = true,
+ feeRate = 8uL,
isTransfer = true,
- doesExist = true,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
transferTxId = "transferTxId",
- createdAt = null,
- updatedAt = null,
)
)
)
@@ -321,25 +280,20 @@ private fun Preview() {
// Onchain Removed
ActivityIcon(
activity = Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = "test-onchain-2",
txType = PaymentType.SENT,
txId = "abc123",
value = 100000uL,
fee = 500uL,
- feeRate = 8uL,
address = "bc1...",
- confirmed = true,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
+ confirmed = true,
+ feeRate = 8uL,
isBoosted = true,
- boostTxIds = emptyList(),
- isTransfer = false,
doesExist = false,
confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(),
- channelId = null,
transferTxId = "transferTxId",
- createdAt = null,
- updatedAt = null,
)
)
)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
index a7e330f1f..856131218 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
@@ -47,7 +47,7 @@ import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems
import to.bitkit.ui.settingsViewModel
import to.bitkit.ui.shared.UiConstants
import to.bitkit.ui.shared.animations.BalanceAnimations
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.theme.Shapes
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt
index c4d62599b..55a1b4fd9 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import to.bitkit.ui.components.CaptionB
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.Colors
@Composable
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/EmptyActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/EmptyActivityRow.kt
index 3d8c95643..83b0e4842 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/EmptyActivityRow.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/EmptyActivityRow.kt
@@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp
import to.bitkit.R
import to.bitkit.ui.components.BodyMSB
import to.bitkit.ui.components.CaptionB
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt
index bc99e8794..ed429302e 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt
@@ -5,6 +5,7 @@ import com.synonym.bitkitcore.LightningActivity
import com.synonym.bitkitcore.OnchainActivity
import com.synonym.bitkitcore.PaymentState
import com.synonym.bitkitcore.PaymentType
+import to.bitkit.ext.create
import java.util.Calendar
val previewActivityItems = buildList {
@@ -19,19 +20,17 @@ val previewActivityItems = buildList {
// Today
add(
Activity.Onchain(
- OnchainActivity(
+ OnchainActivity.create(
id = "1",
txType = PaymentType.RECEIVED,
txId = "01",
value = 42_000_u,
fee = 200_u,
- feeRate = 1_u,
address = "bc1",
confirmed = true,
timestamp = today.epochSecond(),
isBoosted = true,
boostTxIds = listOf("02", "03"),
- isTransfer = false,
doesExist = false,
confirmTimestamp = today.epochSecond(),
channelId = "channelId",
@@ -45,18 +44,16 @@ val previewActivityItems = buildList {
// Yesterday
add(
Activity.Lightning(
- LightningActivity(
+ LightningActivity.create(
id = "2",
txType = PaymentType.SENT,
status = PaymentState.PENDING,
value = 30_000_u,
- fee = 15_u,
invoice = "lnbc2",
- message = "Custom very long lightning activity message to test truncation",
timestamp = yesterday.epochSecond(),
+ fee = 15_u,
+ message = "Custom very long lightning activity message to test truncation",
preimage = "preimage1",
- createdAt = yesterday.epochSecond(),
- updatedAt = yesterday.epochSecond(),
)
)
)
@@ -64,18 +61,15 @@ val previewActivityItems = buildList {
// This Week
add(
Activity.Lightning(
- LightningActivity(
+ LightningActivity.create(
id = "3",
txType = PaymentType.RECEIVED,
status = PaymentState.FAILED,
value = 217_000_u,
- fee = 17_u,
invoice = "lnbc3",
- message = "",
timestamp = thisWeek.epochSecond(),
+ fee = 17_u,
preimage = "preimage2",
- createdAt = thisWeek.epochSecond(),
- updatedAt = thisWeek.epochSecond(),
)
)
)
@@ -83,25 +77,18 @@ val previewActivityItems = buildList {
// This Month
add(
Activity.Onchain(
- OnchainActivity(
+ OnchainActivity.create(
id = "4",
txType = PaymentType.SENT,
txId = "04",
value = 950_000_u,
fee = 110_u,
- feeRate = 1_u,
address = "bc1",
- confirmed = false,
timestamp = thisMonth.epochSecond(),
- isBoosted = false,
- boostTxIds = emptyList(),
isTransfer = true,
- doesExist = true,
confirmTimestamp = today.epochSecond() + 3600u,
channelId = "channelId",
transferTxId = "transferTxId",
- createdAt = thisMonth.epochSecond(),
- updatedAt = thisMonth.epochSecond(),
)
)
)
@@ -109,18 +96,14 @@ val previewActivityItems = buildList {
// Last Year
add(
Activity.Lightning(
- LightningActivity(
+ LightningActivity.create(
id = "5",
txType = PaymentType.SENT,
status = PaymentState.SUCCEEDED,
value = 200_000_u,
- fee = 1_u,
invoice = "lnbc…",
- message = "",
timestamp = lastYear.epochSecond(),
- preimage = null,
- createdAt = lastYear.epochSecond(),
- updatedAt = lastYear.epochSecond(),
+ fee = 1_u,
)
)
)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt
index 660d311a1..f77c53a47 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt
@@ -38,7 +38,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.repositories.CurrencyState
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveAmountScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveAmountScreen.kt
index c0e34149f..f88eba327 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveAmountScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveAmountScreen.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
import to.bitkit.R
@@ -44,8 +44,8 @@ import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.UnitButton
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
index 0dcd6cc64..611224d24 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
@@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Devices.PIXEL_TABLET
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.serialization.Serializable
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveLiquidityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveLiquidityScreen.kt
index 3577ade22..5f3b9c801 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveLiquidityScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveLiquidityScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import to.bitkit.R
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyMB
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt
index db350a194..0b23f52f9 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt
@@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -236,6 +235,7 @@ fun ReceiveQrScreen(
tab = tab,
walletState = walletState,
cjitInvoice = cjitInvoice,
+ onClickEditInvoice = onClickEditInvoice,
modifier = Modifier.weight(1f)
)
}
@@ -354,31 +354,36 @@ private fun ReceiveQrView(
modifier = Modifier.size(18.dp)
)
},
- modifier = Modifier.testTag("SpecifyInvoiceButton")
+ modifier = Modifier
+ .weight(1f)
+ .testTag("SpecifyInvoiceButton")
)
- Tooltip(
- text = stringResource(R.string.wallet__receive_copied),
- tooltipState = qrButtonTooltipState
- ) {
- PrimaryButton(
- text = stringResource(R.string.common__copy),
- size = ButtonSize.Small,
- onClick = {
- context.setClipboardText(uri)
- coroutineScope.launch { qrButtonTooltipState.show() }
- },
- fullWidth = false,
- color = Colors.White10,
- icon = {
- Icon(
- painter = painterResource(R.drawable.ic_copy),
- contentDescription = null,
- tint = tab.accentColor,
- modifier = Modifier.size(18.dp)
- )
- },
- modifier = Modifier.testTag("ReceiveCopyQR")
- )
+ Box(modifier = Modifier.weight(1f)) {
+ Tooltip(
+ text = stringResource(R.string.wallet__receive_copied),
+ tooltipState = qrButtonTooltipState
+ ) {
+ PrimaryButton(
+ text = stringResource(R.string.common__copy),
+ size = ButtonSize.Small,
+ onClick = {
+ context.setClipboardText(uri)
+ coroutineScope.launch { qrButtonTooltipState.show() }
+ },
+ fullWidth = true,
+ color = Colors.White10,
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_copy),
+ contentDescription = null,
+ tint = tab.accentColor,
+ modifier = Modifier.size(18.dp)
+ )
+ },
+ modifier = Modifier
+ .testTag("ReceiveCopyQR")
+ )
+ }
}
PrimaryButton(
text = stringResource(R.string.common__share),
@@ -398,6 +403,7 @@ private fun ReceiveQrView(
modifier = Modifier.size(18.dp)
)
},
+ modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(16.dp))
@@ -452,6 +458,7 @@ private fun ReceiveDetailsView(
tab: ReceiveTab,
walletState: MainUiState,
cjitInvoice: String?,
+ onClickEditInvoice: () -> Unit,
modifier: Modifier = Modifier,
) {
Card(
@@ -471,6 +478,7 @@ private fun ReceiveDetailsView(
),
body = walletState.onchainAddress,
type = CopyAddressType.ONCHAIN,
+ onClickEditInvoice = onClickEditInvoice,
testTag = "ReceiveOnchainAddress",
)
}
@@ -487,6 +495,7 @@ private fun ReceiveDetailsView(
),
body = walletState.onchainAddress,
type = CopyAddressType.ONCHAIN,
+ onClickEditInvoice = onClickEditInvoice,
testTag = "ReceiveOnchainAddress",
)
}
@@ -495,6 +504,7 @@ private fun ReceiveDetailsView(
title = stringResource(R.string.wallet__receive_lightning_invoice),
address = cjitInvoice ?: walletState.bolt11,
type = CopyAddressType.LIGHTNING,
+ onClickEditInvoice = onClickEditInvoice,
testTag = "ReceiveLightningAddress",
)
}
@@ -506,6 +516,7 @@ private fun ReceiveDetailsView(
title = stringResource(R.string.wallet__receive_lightning_invoice),
address = cjitInvoice ?: walletState.bolt11,
type = CopyAddressType.LIGHTNING,
+ onClickEditInvoice = onClickEditInvoice,
testTag = "ReceiveLightningAddress",
)
}
@@ -523,6 +534,7 @@ private fun CopyAddressCard(
title: String,
address: String,
type: CopyAddressType,
+ onClickEditInvoice: () -> Unit,
body: String? = null,
testTag: String? = null,
) {
@@ -536,14 +548,7 @@ private fun CopyAddressCard(
.fillMaxWidth()
.padding(24.dp)
) {
- Row {
- Caption13Up(text = title, color = Colors.White64)
-
- Spacer(modifier = Modifier.width(3.dp))
-
- val iconRes = if (type == CopyAddressType.ONCHAIN) R.drawable.ic_bitcoin else R.drawable.ic_lightning_alt
- Icon(painter = painterResource(iconRes), contentDescription = null, tint = Colors.White64)
- }
+ Caption13Up(text = title, color = Colors.White64)
Spacer(modifier = Modifier.height(16.dp))
BodyS(
text = (body ?: address).truncate(32).uppercase(),
@@ -553,28 +558,48 @@ private fun CopyAddressCard(
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
+ PrimaryButton(
+ text = stringResource(R.string.common__edit),
+ size = ButtonSize.Small,
+ onClick = onClickEditInvoice,
+ fullWidth = false,
+ color = Colors.White10,
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_pencil_simple),
+ contentDescription = null,
+ tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple,
+ modifier = Modifier.size(18.dp)
+ )
+ },
+ modifier = Modifier
+ .weight(1f)
+ .testTag("SpecifyInvoiceButton")
+ )
Tooltip(
text = stringResource(R.string.wallet__receive_copied),
tooltipState = tooltipState,
) {
- PrimaryButton(
- text = stringResource(R.string.common__copy),
- size = ButtonSize.Small,
- onClick = {
- context.setClipboardText(address)
- coroutineScope.launch { tooltipState.show() }
- },
- fullWidth = false,
- color = Colors.White10,
- icon = {
- Icon(
- painter = painterResource(R.drawable.ic_copy),
- contentDescription = null,
- tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple,
- modifier = Modifier.size(18.dp)
- )
- },
- )
+ Box(modifier = Modifier.weight(1f)) {
+ PrimaryButton(
+ text = stringResource(R.string.common__copy),
+ size = ButtonSize.Small,
+ onClick = {
+ context.setClipboardText(address)
+ coroutineScope.launch { tooltipState.show() }
+ },
+ fullWidth = false,
+ color = Colors.White10,
+ icon = {
+ Icon(
+ painter = painterResource(R.drawable.ic_copy),
+ contentDescription = null,
+ tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple,
+ modifier = Modifier.size(18.dp)
+ )
+ },
+ )
+ }
}
PrimaryButton(
text = stringResource(R.string.common__share),
@@ -804,6 +829,7 @@ private fun PreviewDetailsMode() {
bolt11 = "lnbcrt500u1pn7umn7pp5x0s9lt9fwrff6rp70pz3guwnjgw97sjuv79...",
),
cjitInvoice = null,
+ onClickEditInvoice = {},
modifier = Modifier.weight(1f)
)
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt
index 0da519f81..3cee808dc 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt
@@ -11,7 +11,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
@@ -25,7 +25,6 @@ import to.bitkit.ui.walletViewModel
import to.bitkit.viewmodels.AmountInputViewModel
import to.bitkit.viewmodels.MainUiState
import to.bitkit.viewmodels.SettingsViewModel
-import to.bitkit.viewmodels.WalletViewModelEffects
@Composable
fun ReceiveSheet(
@@ -65,16 +64,6 @@ fun ReceiveSheet(
showCreateCjit.value = !cjitInvoice.value.isNullOrBlank()
}
- LaunchedEffect(Unit) {
- wallet.walletEffect.collect { effect ->
- when (effect) {
- WalletViewModelEffects.NavigateGeoBlockScreen -> {
- navController.navigate(ReceiveRoute.GeoBlock)
- }
- }
- }
- }
-
ReceiveQrScreen(
cjitInvoice = cjitInvoice.value,
walletState = walletState,
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/AddTagScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/AddTagScreen.kt
index 719039b16..1de166d58 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/AddTagScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/AddTagScreen.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt
index 25108cf0d..ebad53ac0 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices.NEXUS_5
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.synonym.bitkitcore.LnurlPayData
import com.synonym.bitkitcore.LnurlWithdrawData
@@ -51,8 +51,8 @@ import to.bitkit.ui.components.Text13Up
import to.bitkit.ui.components.UnitButton
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
index 87c6cf7c8..96b032de3 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.lightningdevkit.ldknode.OutPoint
import org.lightningdevkit.ldknode.SpendableUtxo
@@ -44,8 +44,8 @@ import to.bitkit.ui.components.TagButton
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.currencyViewModel
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppSwitchDefaults
import to.bitkit.ui.theme.AppThemeSurface
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt
index a839418ac..19a573b31 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt
@@ -70,8 +70,8 @@ import to.bitkit.ui.components.rememberMoneyText
import to.bitkit.ui.scaffold.AppAlertDialog
import to.bitkit.ui.scaffold.SheetTopBar
import to.bitkit.ui.settingsViewModel
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendFeeRateScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendFeeRateScreen.kt
index 6da22c346..32a0633ef 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendFeeRateScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendFeeRateScreen.kt
@@ -41,8 +41,8 @@ import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.components.settings.SectionHeader
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt
index 8c2df8bde..413573e47 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt
@@ -32,8 +32,8 @@ import to.bitkit.ui.components.NumberPad
import to.bitkit.ui.components.NumberPadType
import to.bitkit.ui.components.PinDots
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt
index 1b6ea536b..186b85b88 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.models.NodeLifecycleState
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/suggestion/BuyIntroScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/suggestion/BuyIntroScreen.kt
index 2f586b053..96c924ac3 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/suggestion/BuyIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/suggestion/BuyIntroScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import to.bitkit.R
import to.bitkit.env.Env
import to.bitkit.models.Suggestion
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt
index 31ca28913..300d8a5d3 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt
@@ -19,12 +19,13 @@ import to.bitkit.ui.theme.AppThemeSurface
@Composable
fun AddWidgetsScreen(
onWidgetSelected: (WidgetType) -> Unit,
+ onBackCLick: () -> Unit,
fiatSymbol: String,
) {
ScreenColumn {
AppTopBar(
titleText = stringResource(R.string.widgets__add),
- onBackClick = null,
+ onBackClick = onBackCLick,
actions = { DrawerNavIcon() },
)
@@ -99,7 +100,8 @@ private fun Preview() {
AppThemeSurface {
AddWidgetsScreen(
onWidgetSelected = {},
- fiatSymbol = "$"
+ fiatSymbol = "$",
+ onBackCLick = {}
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/WidgetsIntroScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/WidgetsIntroScreen.kt
index a92100477..03a9e20c0 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/WidgetsIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/WidgetsIntroScreen.kt
@@ -27,11 +27,12 @@ import to.bitkit.ui.utils.withAccent
@Composable
fun WidgetsIntroScreen(
onContinue: () -> Unit,
+ onBackClick: () -> Unit,
) {
ScreenColumn {
AppTopBar(
titleText = "",
- onBackClick = null,
+ onBackClick = onBackClick,
actions = { DrawerNavIcon() },
)
@@ -68,7 +69,8 @@ fun WidgetsIntroScreen(
private fun Preview() {
AppThemeSurface {
WidgetsIntroScreen(
- onContinue = {}
+ onContinue = {},
+ onBackClick = {}
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/CalculatorPreviewScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/CalculatorPreviewScreen.kt
index 322609712..0a9678b65 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/CalculatorPreviewScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/CalculatorPreviewScreen.kt
@@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/components/CalculatorCard.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/components/CalculatorCard.kt
index 5ddff79ce..7888d6000 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/components/CalculatorCard.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/calculator/components/CalculatorCard.kt
@@ -29,7 +29,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.models.BITCOIN_SYMBOL
diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt
index d8c7917b0..1bd2beb90 100644
--- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import to.bitkit.R
import to.bitkit.ui.Routes
diff --git a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt
index 969c60bbb..558d127f2 100644
--- a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt
@@ -47,12 +47,13 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.settingsViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.viewmodels.BackupCategoryUiState
import to.bitkit.viewmodels.BackupStatusUiState
import to.bitkit.viewmodels.toUiState
+import kotlin.time.ExperimentalTime
@Composable
fun BackupSettingsScreen(
@@ -145,6 +146,7 @@ private fun BackupSettingsScreenContent(
}
}
+@OptIn(ExperimentalTime::class)
@Composable
private fun BackupStatusItem(
uiState: BackupCategoryUiState,
@@ -153,7 +155,7 @@ private fun BackupStatusItem(
) {
val status = uiState.status
- val timeString = if (status.synced == 0L) {
+ val time = if (status.synced == 0L) {
stringResource(R.string.common__never)
} else {
status.synced.toRelativeTimeString()
@@ -161,11 +163,8 @@ private fun BackupStatusItem(
val subtitle = when {
status.running -> stringResource(R.string.settings__backup__status_running)
- !status.isRequired -> stringResource(R.string.settings__backup__status_success)
- .replace("{time}", timeString)
-
- else -> stringResource(R.string.settings__backup__status_failed)
- .replace("{time}", timeString)
+ !status.isRequired -> stringResource(R.string.settings__backup__status_success).replace("{time}", time)
+ else -> stringResource(R.string.settings__backup__status_failed).replace("{time}", time)
}
Row(
diff --git a/app/src/main/java/to/bitkit/ui/settings/BlocktankRegtestScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BlocktankRegtestScreen.kt
index 00c7489d4..47778d650 100644
--- a/app/src/main/java/to/bitkit/ui/settings/BlocktankRegtestScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/BlocktankRegtestScreen.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import kotlinx.coroutines.launch
diff --git a/app/src/main/java/to/bitkit/ui/settings/ChannelOrdersScreen.kt b/app/src/main/java/to/bitkit/ui/settings/ChannelOrdersScreen.kt
index 3c9fb99fb..ed5acd4ed 100644
--- a/app/src/main/java/to/bitkit/ui/settings/ChannelOrdersScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/ChannelOrdersScreen.kt
@@ -55,7 +55,7 @@ import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.components.settings.SectionHeader
import to.bitkit.ui.scaffold.AppTopBar
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppShapes
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt
index bc011093a..3b21f3a2d 100644
--- a/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt
@@ -10,7 +10,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.models.Language
import to.bitkit.ui.components.Text13Up
diff --git a/app/src/main/java/to/bitkit/ui/settings/LogsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/LogsScreen.kt
index 616861093..0d7607cb3 100644
--- a/app/src/main/java/to/bitkit/ui/settings/LogsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/LogsScreen.kt
@@ -32,14 +32,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import to.bitkit.ui.components.BodyMSB
import to.bitkit.ui.components.Caption
import to.bitkit.ui.navigateToLogDetail
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.Colors
import to.bitkit.viewmodels.LogsViewModel
diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt
index 490fd3da6..823a5d24d 100644
--- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt
@@ -40,7 +40,7 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.settingsViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
private const val DEV_MODE_TAP_THRESHOLD = 5
@@ -65,6 +65,7 @@ fun SettingsScreen(
onSupportClick = { navController.navigate(Routes.Support) },
onAboutClick = { navController.navigateToAboutSettings() },
onDevClick = { navController.navigateToDevSettings() },
+ onBackClick = { navController.popBackStack() },
onCogTap = {
haptic.performHapticFeedback(HapticFeedbackType.Confirm)
enableDevModeTapCount = enableDevModeTapCount + 1
@@ -100,11 +101,12 @@ fun SettingsScreenContent(
onAboutClick: () -> Unit,
onDevClick: () -> Unit,
onCogTap: () -> Unit,
+ onBackClick: () -> Unit,
) {
ScreenColumn {
AppTopBar(
titleText = stringResource(R.string.settings__settings),
- onBackClick = null,
+ onBackClick = onBackClick,
actions = { DrawerNavIcon() },
)
Column(
@@ -186,6 +188,7 @@ private fun Preview() {
onAboutClick = {},
onDevClick = {},
onCogTap = {},
+ onBackClick = {},
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/settings/advanced/AddressViewerScreen.kt b/app/src/main/java/to/bitkit/ui/settings/advanced/AddressViewerScreen.kt
index deb9dd4fa..59a555bb5 100644
--- a/app/src/main/java/to/bitkit/ui/settings/advanced/AddressViewerScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/advanced/AddressViewerScreen.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import to.bitkit.R
@@ -45,7 +45,7 @@ import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppShapes
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/settings/advanced/CoinSelectPreferenceScreen.kt b/app/src/main/java/to/bitkit/ui/settings/advanced/CoinSelectPreferenceScreen.kt
index c51cf5e34..9555246b5 100644
--- a/app/src/main/java/to/bitkit/ui/settings/advanced/CoinSelectPreferenceScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/advanced/CoinSelectPreferenceScreen.kt
@@ -12,7 +12,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/settings/advanced/ElectrumConfigScreen.kt b/app/src/main/java/to/bitkit/ui/settings/advanced/ElectrumConfigScreen.kt
index 00322eb29..cd091065f 100644
--- a/app/src/main/java/to/bitkit/ui/settings/advanced/ElectrumConfigScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/advanced/ElectrumConfigScreen.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
diff --git a/app/src/main/java/to/bitkit/ui/settings/advanced/RgsServerScreen.kt b/app/src/main/java/to/bitkit/ui/settings/advanced/RgsServerScreen.kt
index 36d0b52c6..cf74847db 100644
--- a/app/src/main/java/to/bitkit/ui/settings/advanced/RgsServerScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/advanced/RgsServerScreen.kt
@@ -20,7 +20,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
diff --git a/app/src/main/java/to/bitkit/ui/settings/appStatus/AppStatusScreen.kt b/app/src/main/java/to/bitkit/ui/settings/appStatus/AppStatusScreen.kt
index b756c3ac9..9fe6a8799 100644
--- a/app/src/main/java/to/bitkit/ui/settings/appStatus/AppStatusScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/appStatus/AppStatusScreen.kt
@@ -24,10 +24,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
-import kotlinx.datetime.Clock
import to.bitkit.R
import to.bitkit.ext.startActivityAppSettings
import to.bitkit.ext.toLocalizedTimestamp
@@ -42,10 +41,12 @@ import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
+import kotlin.time.Clock.System.now
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
@Composable
fun AppStatusScreen(
@@ -246,6 +247,7 @@ private data class StatusUi(
val state: HealthState,
)
+@OptIn(ExperimentalTime::class)
@Preview(showSystemUi = true)
@Composable
private fun Preview() {
@@ -259,7 +261,7 @@ private fun Preview() {
channels = HealthState.PENDING,
backups = HealthState.READY,
),
- backupSubtitle = Clock.System.now().minus(3.minutes).toEpochMilliseconds().toLocalizedTimestamp(),
+ backupSubtitle = now().minus(3.minutes).toEpochMilliseconds().toLocalizedTimestamp(),
nodeSubtitle = NodeLifecycleState.Running.uiText,
),
)
diff --git a/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsIntroScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsIntroScreen.kt
index 01b15e798..08204ea77 100644
--- a/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsIntroScreen.kt
@@ -11,7 +11,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import to.bitkit.R
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.Display
diff --git a/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsSettings.kt b/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsSettings.kt
index 257ced12b..562bbfe62 100644
--- a/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsSettings.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/backgroundPayments/BackgroundPaymentsSettings.kt
@@ -12,7 +12,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/BackupIntroScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/BackupIntroScreen.kt
index cd564c469..4efd1aa55 100644
--- a/app/src/main/java/to/bitkit/ui/settings/backups/BackupIntroScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/backups/BackupIntroScreen.kt
@@ -4,10 +4,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
@@ -25,6 +23,7 @@ import to.bitkit.ui.components.Display
import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.components.SheetSize
+import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.SheetTopBar
import to.bitkit.ui.shared.modifiers.sheetHeight
import to.bitkit.ui.shared.util.gradientBackground
@@ -47,7 +46,6 @@ fun BackupIntroScreen(
.testTag("BackupIntroView")
) {
SheetTopBar(stringResource(R.string.security__backup_wallet))
-
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 16.dp),
@@ -60,30 +58,28 @@ fun BackupIntroScreen(
.weight(1f)
.testTag("BackupIntroViewImage")
)
-
Display(
text = stringResource(R.string.security__backup_title).withAccent(accentColor = Colors.Blue),
color = Colors.White,
modifier = Modifier
.testTag("BackupIntroViewTitle")
)
- Spacer(Modifier.height(8.dp))
+ VerticalSpacer(8.dp)
BodyM(
- text = if (hasFunds) {
- stringResource(R.string.security__backup_funds)
- } else {
- stringResource(R.string.security__backup_funds_no)
+ text = when (hasFunds) {
+ true -> stringResource(R.string.security__backup_funds)
+ else -> stringResource(R.string.security__backup_funds_no)
},
color = Colors.White64,
modifier = Modifier
.testTag("BackupIntroViewDescription")
)
- Spacer(Modifier.height(32.dp))
+ VerticalSpacer(32.dp)
Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxWidth()
- .testTag("BackupIntroViewButtons"),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
+ .testTag("BackupIntroViewButtons")
) {
SecondaryButton(
text = stringResource(R.string.common__later),
@@ -91,19 +87,18 @@ fun BackupIntroScreen(
onClick = onClose,
modifier = Modifier
.weight(1f)
- .testTag("BackupIntroViewCancel"),
+ .testTag("BackupIntroViewCancel")
)
-
PrimaryButton(
text = stringResource(R.string.security__backup_button),
fullWidth = false,
onClick = onConfirm,
modifier = Modifier
.weight(1f)
- .testTag("BackupIntroViewContinue"),
+ .testTag("BackupIntroViewContinue")
)
}
- Spacer(Modifier.height(16.dp))
+ VerticalSpacer(16.dp)
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt
index f90906e27..cc63bfc56 100644
--- a/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt
@@ -12,7 +12,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt
index 992f70a0a..3081689de 100644
--- a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt
@@ -71,7 +71,7 @@ import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.settings.lightning.components.ChannelStatusView
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.getBlockExplorerUrl
diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt
index 32618dc1a..d409a81b3 100644
--- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt
@@ -60,7 +60,7 @@ import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.navigateToTransferFunding
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.shareZipFile
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt
index 71bbae2e9..8913265f2 100644
--- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt
@@ -34,15 +34,19 @@ fun ChangePinResultScreen(
onOkClick = {
navController.popBackStack(inclusive = false)
},
+ onBackClick = {
+ navController.popBackStack()
+ }
)
}
@Composable
private fun ChangePinResultContent(
onOkClick: () -> Unit,
+ onBackClick: () -> Unit,
) {
ScreenColumn {
- AppTopBar(stringResource(R.string.security__cp_changed_title), onBackClick = null)
+ AppTopBar(stringResource(R.string.security__cp_changed_title), onBackClick = onBackClick)
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
@@ -81,6 +85,7 @@ private fun Preview() {
AppThemeSurface {
ChangePinResultContent(
onOkClick = {},
+ onBackClick = {},
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt
index 4a199c10f..85c727ed0 100644
--- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt
@@ -33,7 +33,7 @@ import to.bitkit.ui.navigateToChangePinNew
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinBiometricsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinBiometricsScreen.kt
index c8345e6c2..b727056eb 100644
--- a/app/src/main/java/to/bitkit/ui/settings/pin/PinBiometricsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinBiometricsScreen.kt
@@ -38,7 +38,7 @@ import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.SecondaryButton
import to.bitkit.ui.scaffold.SheetTopBar
import to.bitkit.ui.settingsViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppSwitchDefaults
import to.bitkit.ui.theme.AppThemeSurface
diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinResultScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinResultScreen.kt
index 4e2c58247..a3eba7734 100644
--- a/app/src/main/java/to/bitkit/ui/settings/pin/PinResultScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinResultScreen.kt
@@ -29,7 +29,7 @@ import to.bitkit.ui.components.BodyMSB
import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.scaffold.SheetTopBar
import to.bitkit.ui.settingsViewModel
-import to.bitkit.ui.shared.util.clickableAlpha
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppSwitchDefaults
import to.bitkit.ui.theme.AppThemeSurface
diff --git a/app/src/main/java/to/bitkit/ui/settings/quickPay/QuickPaySettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/quickPay/QuickPaySettingsScreen.kt
index 5978da09d..2c1cf66da 100644
--- a/app/src/main/java/to/bitkit/ui/settings/quickPay/QuickPaySettingsScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/quickPay/QuickPaySettingsScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/settings/support/ReportIssueScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/ReportIssueScreen.kt
index e4dc66b40..4fc4689c9 100644
--- a/app/src/main/java/to/bitkit/ui/settings/support/ReportIssueScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/settings/support/ReportIssueScreen.kt
@@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.ui.components.BodyM
diff --git a/app/src/main/java/to/bitkit/ui/shared/modifiers/ClickableAlpha.kt b/app/src/main/java/to/bitkit/ui/shared/modifiers/ClickableAlpha.kt
new file mode 100644
index 000000000..555d79d06
--- /dev/null
+++ b/app/src/main/java/to/bitkit/ui/shared/modifiers/ClickableAlpha.kt
@@ -0,0 +1,138 @@
+package to.bitkit.ui.shared.modifiers
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.unit.Constraints
+import kotlinx.coroutines.launch
+
+/**
+ * Adjusts the alpha of a composable when it is pressed and makes it clickable.
+ * When pressed, the alpha is reduced to provide visual feedback.
+ * If `onClick` is null, the clickable behavior is disabled.
+ *
+ * Analogue of `TouchableOpacity` in React Native.
+ */
+fun Modifier.clickableAlpha(
+ pressedAlpha: Float = 0.7f,
+ onClick: (() -> Unit)?,
+): Modifier = if (onClick != null) {
+ this.then(ClickableAlphaElement(pressedAlpha, onClick))
+} else {
+ this
+}
+
+private data class ClickableAlphaElement(
+ val pressedAlpha: Float,
+ val onClick: () -> Unit,
+) : ModifierNodeElement() {
+ override fun create(): ClickableAlphaNode = ClickableAlphaNode(pressedAlpha, onClick)
+
+ override fun update(node: ClickableAlphaNode) {
+ node.pressedAlpha = pressedAlpha
+ node.onClick = onClick
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "clickableAlpha"
+ properties["pressedAlpha"] = pressedAlpha
+ properties["onClick"] = onClick
+ }
+}
+
+private class ClickableAlphaNode(
+ var pressedAlpha: Float,
+ var onClick: () -> Unit,
+) : DelegatingNode(), LayoutModifierNode, SemanticsModifierNode {
+
+ private val animatable = Animatable(1f)
+
+ init {
+ delegate(
+ SuspendingPointerInputModifierNode {
+ detectTapGestures(
+ onPress = {
+ coroutineScope.launch { animatable.animateTo(pressedAlpha) }
+ val released = tryAwaitRelease()
+ if (!released) {
+ coroutineScope.launch { animatable.animateTo(1f) }
+ }
+ },
+ onTap = {
+ onClick()
+ coroutineScope.launch {
+ animatable.animateTo(pressedAlpha)
+ animatable.animateTo(1f)
+ }
+ }
+ )
+ }
+ )
+ }
+
+ override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
+ val placeable = measurable.measure(constraints)
+
+ return layout(placeable.width, placeable.height) {
+ placeable.placeWithLayer(0, 0) {
+ this.alpha = animatable.value
+ }
+ }
+ }
+
+ override fun SemanticsPropertyReceiver.applySemantics() {
+ role = Role.Button
+ onClick {
+ onClick()
+ true
+ }
+ }
+}
+
+/**
+ * Applies alpha animation feedback on press without consuming click events.
+ * This allows the Button's onClick to work while providing full-area visual feedback.
+ */
+@Composable
+fun Modifier.alphaFeedback(
+ pressedAlpha: Float = 0.7f,
+ enabled: Boolean = true,
+): Modifier = if (enabled) {
+ val animatable = remember { Animatable(1f) }
+ val scope = rememberCoroutineScope()
+
+ this
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ scope.launch { animatable.animateTo(pressedAlpha) }
+ tryAwaitRelease()
+ scope.launch { animatable.animateTo(1f) }
+ },
+ onTap = null // Don't consume tap - let Button handle it
+ )
+ }
+ .graphicsLayer {
+ alpha = animatable.value
+ }
+} else {
+ this
+}
diff --git a/app/src/main/java/to/bitkit/ui/shared/toast/ToastQueueManager.kt b/app/src/main/java/to/bitkit/ui/shared/toast/ToastQueueManager.kt
index 680d2f048..94af44aa3 100644
--- a/app/src/main/java/to/bitkit/ui/shared/toast/ToastQueueManager.kt
+++ b/app/src/main/java/to/bitkit/ui/shared/toast/ToastQueueManager.kt
@@ -49,8 +49,7 @@ class ToastQueueManager(private val scope: CoroutineScope) {
}
newQueue
}
- // If no toast is currently displayed, show this one immediately
- showNextToastIfAvailable()
+ dismissCurrentToast()
}
/**
diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt
index 596170d32..1568ad651 100644
--- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt
+++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt
@@ -1,22 +1,13 @@
package to.bitkit.ui.shared.util
-import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
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
import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.shadow
@@ -27,7 +18,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.node.DrawModifierNode
@@ -37,54 +27,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import to.bitkit.ui.theme.Colors
-/**
- * Adjusts the alpha of a composable when it is pressed and makes it clickable.
- * When pressed, the alpha is reduced to provide visual feedback.
- * If `onClick` is null, the clickable behavior is disabled.
- *
- * Analogue of `TouchableOpacity` in React Native.
- */
-fun Modifier.clickableAlpha(
- pressedAlpha: Float = 0.7f,
- onClick: (() -> Unit)?,
-): Modifier = composed {
- val interactionSource = remember { MutableInteractionSource() }
- val isPressed by interactionSource.collectIsPressedAsState()
-
- val wasClicked = remember { mutableStateOf(false) }
-
- LaunchedEffect(isPressed) {
- if (!isPressed) {
- wasClicked.value = false
- }
- }
-
- val alpha by animateFloatAsState(
- targetValue = if (isPressed || wasClicked.value) pressedAlpha else 1f,
- finishedListener = {
- // Reset the clicked state after animation completes
- wasClicked.value = false
- }
- )
-
- this
- .graphicsLayer { this.alpha = alpha }
- .then(
- if (onClick != null) {
- Modifier.clickable(
- onClick = {
- wasClicked.value = true
- onClick()
- },
- interactionSource = interactionSource,
- indication = null,
- )
- } else {
- Modifier
- }
- )
-}
-
fun Modifier.gradientBackground(
startColor: Color = Colors.White08,
endColor: Color = Color.White.copy(alpha = 0.012f),
diff --git a/app/src/main/java/to/bitkit/ui/sheets/BackupSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/BackupSheet.kt
index bc9bd5cee..0ccaf924b 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/BackupSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/BackupSheet.kt
@@ -9,7 +9,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
diff --git a/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionSheet.kt
index f45ce4317..f9d38456a 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionSheet.kt
@@ -30,7 +30,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.synonym.bitkitcore.Activity
import to.bitkit.R
import to.bitkit.models.BITCOIN_SYMBOL
@@ -48,8 +48,8 @@ import to.bitkit.ui.components.SwipeToConfirm
import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.components.rememberMoneyText
import to.bitkit.ui.scaffold.SheetTopBar
+import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.sheetHeight
-import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
diff --git a/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionViewModel.kt b/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionViewModel.kt
index 50f226f42..051c1465f 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionViewModel.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/BoostTransactionViewModel.kt
@@ -211,6 +211,8 @@ class BoostTransactionViewModel @Inject constructor(
Logger.debug("Boost successful. newTxId: $newTxId", context = TAG)
updateActivity(newTxId = newTxId, isRBF = isRBF).fold(
onSuccess = {
+ lightningRepo.sync()
+ activityRepo.syncActivities()
_uiState.update { it.copy(boosting = false) }
setBoostTransactionEffect(BoostTransactionEffects.OnBoostSuccess)
},
diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt
index 380d9d8f4..8d286dfbc 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt
@@ -8,7 +8,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import to.bitkit.R
diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt
index 342a5a110..d7a1bc8e4 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import to.bitkit.di.BgDispatcher
+import to.bitkit.ext.create
import to.bitkit.ext.nowTimestamp
import to.bitkit.models.NewTransactionSheetDetails
import to.bitkit.models.NewTransactionSheetDirection
@@ -98,18 +99,14 @@ class GiftViewModel @Inject constructor(
private suspend fun insertGiftActivity(result: GiftClaimResult.SuccessWithoutLiquidity) {
val nowTimestamp = nowTimestamp().epochSecond.toULong()
- val lightningActivity = LightningActivity(
+ val lightningActivity = LightningActivity.create(
id = result.paymentHashOrTxId,
txType = PaymentType.RECEIVED,
status = PaymentState.SUCCEEDED,
value = result.sats.toULong(),
- fee = 0u,
invoice = result.invoice,
- message = result.code,
timestamp = nowTimestamp,
- preimage = null,
- createdAt = nowTimestamp,
- updatedAt = null,
+ message = result.code,
)
activityRepo.insertActivity(Activity.Lightning(lightningActivity)).getOrThrow()
diff --git a/app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt
index 2bfe00004..5c4f49e59 100644
--- a/app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt
+++ b/app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt
@@ -11,7 +11,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
diff --git a/app/src/main/java/to/bitkit/utils/Errors.kt b/app/src/main/java/to/bitkit/utils/Errors.kt
index 7f0d63088..9796db4bf 100644
--- a/app/src/main/java/to/bitkit/utils/Errors.kt
+++ b/app/src/main/java/to/bitkit/utils/Errors.kt
@@ -128,3 +128,7 @@ class LdkError(private val inner: LdkException) : AppError("Unknown LDK error.")
}
}
// endregion
+
+/** Check if the throwable is a TxSyncTimeout exception. */
+fun Throwable.isTxSyncTimeout(): Boolean =
+ this is NodeException.TxSyncTimeout || cause is NodeException.TxSyncTimeout
diff --git a/app/src/main/java/to/bitkit/utils/Logger.kt b/app/src/main/java/to/bitkit/utils/Logger.kt
index ade0299aa..85aa44e1c 100644
--- a/app/src/main/java/to/bitkit/utils/Logger.kt
+++ b/app/src/main/java/to/bitkit/utils/Logger.kt
@@ -142,7 +142,7 @@ class LoggerImpl(
path: String = getCallerPath(),
line: Int = getCallerLine(),
) {
- val errMsg = e?.let { "[${e::class.simpleName}='${e.message}']" }.orEmpty()
+ val errMsg = e?.let { errLogOf(it) }.orEmpty()
val message = formatLog(LogLevel.WARN, "$msg $errMsg", context, path, line)
if (compact) Log.w(tag, message) else Log.w(tag, message, e)
saver.save(message)
@@ -155,7 +155,7 @@ class LoggerImpl(
path: String = getCallerPath(),
line: Int = getCallerLine(),
) {
- val errMsg = e?.let { "[${e::class.simpleName}='${e.message}']" }.orEmpty()
+ val errMsg = e?.let { errLogOf(it) }.orEmpty()
val message = formatLog(LogLevel.ERROR, "$msg $errMsg", context, path, line)
if (compact) Log.e(tag, message) else Log.e(tag, message, e)
saver.save(message)
@@ -339,3 +339,5 @@ val jsonLogger = Json(json) {
inline fun jsonLogOf(value: T): String = with(jsonLogger) {
encodeToString(serializersModule.serializer(), value)
}
+
+fun errLogOf(e: Throwable): String = "[${e::class.simpleName}='${e.message}']"
diff --git a/app/src/main/java/to/bitkit/utils/Perf.kt b/app/src/main/java/to/bitkit/utils/Perf.kt
index 46a13c4d1..c79176d9b 100644
--- a/app/src/main/java/to/bitkit/utils/Perf.kt
+++ b/app/src/main/java/to/bitkit/utils/Perf.kt
@@ -4,17 +4,16 @@ import java.time.Instant
import kotlin.system.measureTimeMillis
internal inline fun measured(
- functionName: String,
+ label: String,
block: () -> T,
): T {
var result: T
- val elapsed = measureTimeMillis {
+ val elapsedMs = measureTimeMillis {
result = block()
- }.let { it / 1000.0 }
+ }
- val threadName = Thread.currentThread().name
- Logger.performance("$functionName took $elapsed seconds on $threadName")
+ Logger.debug("$label took ${elapsedMs}ms")
return result
}
diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
index 41028fcd0..232d496dd 100644
--- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
+++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
@@ -46,7 +46,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.Event
import org.lightningdevkit.ldknode.PaymentId
import org.lightningdevkit.ldknode.SpendableUtxo
@@ -69,6 +68,7 @@ import to.bitkit.ext.maxSendableSat
import to.bitkit.ext.maxWithdrawableSat
import to.bitkit.ext.minSendableSat
import to.bitkit.ext.minWithdrawableSat
+import to.bitkit.ext.nowMillis
import to.bitkit.ext.rawId
import to.bitkit.ext.removeSpaces
import to.bitkit.ext.setClipboardText
@@ -93,6 +93,7 @@ import to.bitkit.repositories.CurrencyRepo
import to.bitkit.repositories.HealthRepo
import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.PreActivityMetadataRepo
+import to.bitkit.repositories.TransferRepo
import to.bitkit.repositories.WalletRepo
import to.bitkit.services.AppUpdaterService
import to.bitkit.ui.Routes
@@ -106,14 +107,19 @@ import to.bitkit.utils.Logger
import to.bitkit.utils.jsonLogOf
import java.math.BigDecimal
import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Suppress("LongParameterList")
@HiltViewModel
class AppViewModel @Inject constructor(
connectivityRepo: ConnectivityRepo,
healthRepo: HealthRepo,
- @param:ApplicationContext private val context: Context,
- @param:BgDispatcher private val bgDispatcher: CoroutineDispatcher,
+ toastManagerProvider: @JvmSuppressWildcards (CoroutineScope) -> ToastQueueManager,
+ @ApplicationContext private val context: Context,
+ @BgDispatcher private val bgDispatcher: CoroutineDispatcher,
private val keychain: Keychain,
private val lightningRepo: LightningRepo,
private val walletRepo: WalletRepo,
@@ -126,7 +132,7 @@ class AppViewModel @Inject constructor(
private val appUpdaterService: AppUpdaterService,
private val notifyPaymentReceivedHandler: NotifyPaymentReceivedHandler,
private val cacheStore: CacheStore,
- private val toastManagerProvider: @JvmSuppressWildcards (CoroutineScope) -> ToastQueueManager,
+ private val transferRepo: TransferRepo,
) : ViewModel() {
val healthState = healthRepo.healthState
@@ -241,9 +247,9 @@ class AppViewModel @Inject constructor(
runCatching {
when (event) {
is Event.BalanceChanged -> handleBalanceChanged()
- is Event.ChannelClosed -> Unit
- is Event.ChannelPending -> Unit
- is Event.ChannelReady -> notifyChannelReady(event)
+ is Event.ChannelClosed -> handleChannelClosed()
+ is Event.ChannelPending -> handleChannelPending()
+ is Event.ChannelReady -> handleChannelReady(event)
is Event.OnchainTransactionConfirmed -> handleOnchainTransactionConfirmed(event)
is Event.OnchainTransactionEvicted -> handleOnchainTransactionEvicted(event)
is Event.OnchainTransactionReceived -> handleOnchainTransactionReceived(event)
@@ -260,6 +266,7 @@ class AppViewModel @Inject constructor(
is Event.SyncProgress -> Unit
}
}.onFailure { e ->
+ if (e is CancellationException) throw e
Logger.error("LDK event handler error", e, context = TAG)
}
}
@@ -267,12 +274,24 @@ class AppViewModel @Inject constructor(
private suspend fun handleBalanceChanged() {
walletRepo.syncBalances()
+ transferRepo.syncTransferStates()
}
- private suspend fun handleSyncCompleted() {
- walletRepo.syncNodeAndWallet()
+ private suspend fun handleChannelReady(event: Event.ChannelReady) {
+ transferRepo.syncTransferStates()
+ walletRepo.syncBalances()
+ notifyChannelReady(event)
}
+ private suspend fun handleChannelPending() = transferRepo.syncTransferStates()
+
+ private suspend fun handleChannelClosed() {
+ transferRepo.syncTransferStates()
+ walletRepo.syncBalances()
+ }
+
+ private fun handleSyncCompleted() = walletRepo.debounceSyncByEvent()
+
private suspend fun handleOnchainTransactionConfirmed(event: Event.OnchainTransactionConfirmed) {
activityRepo.handleOnchainTransactionConfirmed(event.txid, event.details)
}
@@ -1706,7 +1725,8 @@ class AppViewModel @Inject constructor(
handleScan(data.removeLightningSchemes())
}
- // Todo Temporaary fix while these schemes can't be decoded
+ // TODO Temporary fix while these schemes can't be decoded
+ @Suppress("SpellCheckingInspection")
private fun String.removeLightningSchemes(): String {
return this
.replace("lnurl:", "")
@@ -1769,31 +1789,29 @@ class AppViewModel @Inject constructor(
}
viewModelScope.launch {
- val currentTime = Clock.System.now().toEpochMilliseconds()
+ val currentTime = nowMillis()
when (currentSheet) {
- TimedSheetType.BACKUP -> {
- settingsStore.update { it.copy(backupWarningIgnoredMillis = currentTime) }
+ TimedSheetType.HIGH_BALANCE -> settingsStore.update {
+ it.copy(
+ balanceWarningTimes = it.balanceWarningTimes + 1,
+ balanceWarningIgnoredMillis = currentTime,
+ )
}
- TimedSheetType.HIGH_BALANCE -> {
- settingsStore.update {
- it.copy(
- balanceWarningTimes = it.balanceWarningTimes + 1,
- balanceWarningIgnoredMillis = currentTime
- )
- }
+ TimedSheetType.NOTIFICATIONS -> settingsStore.update {
+ it.copy(notificationsIgnoredMillis = currentTime)
}
- TimedSheetType.APP_UPDATE -> Unit
-
- TimedSheetType.NOTIFICATIONS -> {
- settingsStore.update { it.copy(notificationsIgnoredMillis = currentTime) }
+ TimedSheetType.BACKUP -> settingsStore.update {
+ it.copy(backupWarningIgnoredMillis = currentTime)
}
- TimedSheetType.QUICK_PAY -> {
- settingsStore.update { it.copy(quickPayIntroSeen = true) }
+ TimedSheetType.QUICK_PAY -> settingsStore.update {
+ it.copy(quickPayIntroSeen = true)
}
+
+ TimedSheetType.APP_UPDATE -> Unit
}
}
diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
index 96a36b22c..1aa39e6a8 100644
--- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
+++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
@@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
-import kotlinx.datetime.Clock
import org.lightningdevkit.ldknode.ChannelDetails
import to.bitkit.R
import to.bitkit.data.CacheStore
@@ -43,13 +42,16 @@ import to.bitkit.utils.Logger
import javax.inject.Inject
import kotlin.math.min
import kotlin.math.roundToLong
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
+import kotlin.time.ExperimentalTime
const val RETRY_INTERVAL_MS = 1 * 60 * 1000L // 1 minutes in ms
const val GIVE_UP_MS = 30 * 60 * 1000L // 30 minutes in ms
@Suppress("LongParameterList")
+@OptIn(ExperimentalTime::class)
@HiltViewModel
class TransferViewModel @Inject constructor(
@ApplicationContext private val context: Context,
@@ -208,7 +210,7 @@ class TransferViewModel @Inject constructor(
lspOrderId = order.id,
)
launch { walletRepo.syncBalances() }
- watchOrder(order.id)
+ launch { watchOrder(order.id) }
}
.onFailure { error ->
ToastEventBus.send(error)
@@ -216,47 +218,67 @@ class TransferViewModel @Inject constructor(
}
}
- private fun watchOrder(orderId: String, frequencyMs: Long = 2_500) {
- var isSettled = false
- var error: Throwable? = null
-
- viewModelScope.launch {
- Logger.debug("Started to watch order '$orderId'", context = TAG)
-
- while (!isSettled && error == null) {
- try {
- Logger.debug("Refreshing order '$orderId'")
- val order = blocktankRepo.getOrder(orderId, refresh = true).getOrNull()
- if (order == null) {
- error = Exception("Order not found '$orderId'").also {
- Logger.error(it.message, context = TAG)
- }
- break
- }
-
- val step = updateOrder(order)
- settingsStore.update { it.copy(lightningSetupStep = step) }
- Logger.debug("LN setup step: $step")
+ private suspend fun watchOrder(orderId: String): Result {
+ Logger.debug("Started watching order: '$orderId'", context = TAG)
+ try {
+ // Step 0: Starting
+ settingsStore.update { it.copy(lightningSetupStep = LN_SETUP_STEP_0) }
+ Logger.debug("LN setup step: $LN_SETUP_STEP_0", context = TAG)
+ delay(MIN_STEP_DELAY_MS)
+
+ // Poll until payment is confirmed (order state becomes PAID or EXECUTED)
+ val paidOrder = pollUntil(orderId) { order ->
+ order.state2 == BtOrderState2.PAID || order.state2 == BtOrderState2.EXECUTED
+ } ?: return Result.failure(Exception("Order not found or expired"))
+
+ // Step 1: Payment confirmed
+ settingsStore.update { it.copy(lightningSetupStep = LN_SETUP_STEP_1) }
+ Logger.debug("LN setup step: $LN_SETUP_STEP_1", context = TAG)
+ delay(MIN_STEP_DELAY_MS)
+
+ // Try to open channel (idempotent - safe to call multiple times)
+ blocktankRepo.openChannel(paidOrder.id)
+
+ // Step 2: Channel opening requested
+ settingsStore.update { it.copy(lightningSetupStep = LN_SETUP_STEP_2) }
+ Logger.debug("LN setup step: $LN_SETUP_STEP_2", context = TAG)
+ delay(MIN_STEP_DELAY_MS)
+
+ // Poll until channel is ready (EXECUTED state or channel has state)
+ pollUntil(orderId) { order ->
+ order.state2 == BtOrderState2.EXECUTED || order.channel?.state != null
+ } ?: return Result.failure(Exception("Order not found or expired"))
+
+ // Step 3: Complete
+ transferRepo.syncTransferStates()
+ settingsStore.update { it.copy(lightningSetupStep = LN_SETUP_STEP_3) }
+ Logger.debug("LN setup step: $LN_SETUP_STEP_3", context = TAG)
+
+ Logger.debug("Order settled: '$orderId'", context = TAG)
+ return Result.success(true)
+ } catch (e: Throwable) {
+ Logger.error("Failed to watch order: '$orderId'", e, context = TAG)
+ return Result.failure(e)
+ } finally {
+ Logger.debug("Stopped watching order: '$orderId'", context = TAG)
+ }
+ }
- if (order.state2 == BtOrderState2.EXPIRED) {
- error = Exception("Order expired '$orderId'").also {
- Logger.error(it.message, context = TAG)
- }
- break
- }
- if (step > 2) {
- Logger.debug("Order settled, stopping watch order '$orderId'", context = TAG)
- isSettled = true
- break
- }
- } catch (e: Throwable) {
- Logger.error("Failed to watch order '$orderId'", e, context = TAG)
- error = e
- break
- }
- delay(frequencyMs)
+ private suspend fun pollUntil(orderId: String, condition: (IBtOrder) -> Boolean): IBtOrder? {
+ while (true) {
+ val order = blocktankRepo.getOrder(orderId, refresh = true).getOrNull()
+ if (order == null) {
+ Logger.error("Order not found: '$orderId'", context = TAG)
+ return null
}
- Logger.debug("Stopped watching order '$orderId'", context = TAG)
+ if (order.state2 == BtOrderState2.EXPIRED) {
+ Logger.error("Order expired: '$orderId'", context = TAG)
+ return null
+ }
+ if (condition(order)) {
+ return order
+ }
+ delay(POLL_INTERVAL_MS)
}
}
@@ -306,26 +328,6 @@ class TransferViewModel @Inject constructor(
}
}
- private suspend fun updateOrder(order: IBtOrder): Int {
- if (order.channel != null) {
- transferRepo.syncTransferStates()
- return LN_SETUP_STEP_3
- }
-
- when (order.state2) {
- BtOrderState2.CREATED -> return 0
-
- BtOrderState2.PAID -> {
- blocktankRepo.openChannel(order.id)
- return 1
- }
-
- BtOrderState2.EXECUTED -> return 2
-
- else -> return 0
- }
- }
-
fun onUseDefaultLspBalanceClick() {
val defaultOrder = _spendingUiState.value.defaultOrder
_spendingUiState.update { it.copy(order = defaultOrder, defaultOrder = null, isAdvanced = false) }
@@ -490,6 +492,11 @@ class TransferViewModel @Inject constructor(
companion object {
private const val TAG = "TransferViewModel"
+ private const val MIN_STEP_DELAY_MS = 500L
+ private const val POLL_INTERVAL_MS = 2_500L
+ const val LN_SETUP_STEP_0 = 0
+ const val LN_SETUP_STEP_1 = 1
+ const val LN_SETUP_STEP_2 = 2
const val LN_SETUP_STEP_3 = 3
}
}
diff --git a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt
index ff03f0084..af73379ec 100644
--- a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt
+++ b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt
@@ -7,11 +7,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -28,12 +26,14 @@ import to.bitkit.repositories.BackupRepo
import to.bitkit.repositories.BlocktankRepo
import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.RecoveryModeException
+import to.bitkit.repositories.SyncSource
import to.bitkit.repositories.WalletRepo
import to.bitkit.ui.onboarding.LOADING_MS
import to.bitkit.ui.shared.toast.ToastEventBus
import to.bitkit.utils.Logger
-import to.bitkit.utils.ServiceError
+import to.bitkit.utils.isTxSyncTimeout
import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration.Companion.milliseconds
@HiltViewModel
@@ -64,9 +64,7 @@ class WalletViewModel @Inject constructor(
@Deprecated("Prioritize get the wallet and lightning states from LightningRepo or WalletRepo")
val uiState = _uiState.asStateFlow()
- private val _walletEffect = MutableSharedFlow(extraBufferCapacity = 1)
- val walletEffect = _walletEffect.asSharedFlow()
- private fun walletEffect(effect: WalletViewModelEffects) = viewModelScope.launch { _walletEffect.emit(effect) }
+ private var syncJob: Job? = null
init {
if (walletExists) {
@@ -142,7 +140,7 @@ class WalletViewModel @Inject constructor(
.onSuccess {
walletRepo.setWalletExistsState()
walletRepo.syncBalances()
- // Skip refreshing during restore, it will be called when it completes
+ // Skip refresh during restore, it will be called after completion
if (restoreState.isIdle()) {
walletRepo.refreshBip21()
}
@@ -172,17 +170,24 @@ class WalletViewModel @Inject constructor(
walletRepo.syncNodeAndWallet()
.onFailure { error ->
Logger.error("Failed to refresh state: ${error.message}", error)
- if (error !is TimeoutCancellationException) {
- ToastEventBus.send(error)
- }
+ if (error is CancellationException || error.isTxSyncTimeout()) return@onFailure
+ ToastEventBus.send(error)
}
}
fun onPullToRefresh() {
- viewModelScope.launch {
+ // Cancel any existing sync, manual or event triggered
+ syncJob?.cancel()
+ walletRepo.cancelSyncByEvent()
+ lightningRepo.clearPendingSync()
+
+ syncJob = viewModelScope.launch {
_uiState.update { it.copy(isRefreshing = true) }
- refreshState().join()
- _uiState.update { it.copy(isRefreshing = false) }
+ try {
+ walletRepo.syncNodeAndWallet(source = SyncSource.MANUAL)
+ } finally {
+ _uiState.update { it.copy(isRefreshing = false) }
+ }
}
}
@@ -220,21 +225,6 @@ class WalletViewModel @Inject constructor(
}
}
- fun toggleReceiveOnSpending() {
- viewModelScope.launch {
- walletRepo.toggleReceiveOnSpendingBalance()
- .onSuccess {
- updateBip21Invoice()
- }.onFailure { e ->
- if (e is ServiceError.GeoBlocked) {
- walletEffect(WalletViewModelEffects.NavigateGeoBlockScreen)
- return@launch
- }
- updateBip21Invoice()
- }
- }
- }
-
fun refreshReceiveState() = viewModelScope.launch {
launch { blocktankRepo.refreshInfo() }
lightningRepo.updateGeoBlockState()
@@ -290,10 +280,6 @@ class WalletViewModel @Inject constructor(
walletRepo.resetPreActivityMetadataTagsForCurrentInvoice()
}
- fun loadTagsForCurrentInvoice() = viewModelScope.launch {
- walletRepo.loadTagsForCurrentInvoice()
- }
-
fun updateBip21Description(newText: String) {
if (newText.isEmpty()) {
Logger.warn("Empty")
@@ -326,10 +312,6 @@ data class MainUiState(
val selectedTags: List = listOf(),
)
-sealed interface WalletViewModelEffects {
- data object NavigateGeoBlockScreen : WalletViewModelEffects
-}
-
sealed interface RestoreState {
data object Initial : RestoreState
sealed interface InProgress : RestoreState {
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
index b7aa4ce87..e63379543 100644
--- a/app/src/main/res/drawable/ic_share.xml
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -1,19 +1,18 @@
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16"
+ android:viewportHeight="16">
+ android:pathData="M10.646,3.146C10.842,2.951 11.158,2.951 11.354,3.146L14.354,6.146C14.549,6.342 14.549,6.658 14.354,6.854L11.354,9.854C11.158,10.049 10.842,10.049 10.646,9.854C10.451,9.658 10.451,9.342 10.646,9.146L13.293,6.5L10.646,3.854C10.451,3.658 10.451,3.342 10.646,3.146Z"
+ android:fillColor="#FF4400"
+ android:fillType="evenOdd"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3950ca793..41729a2dc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1078,6 +1078,7 @@
The transaction was successfully boosted.
Boost Failed
Bitkit was unable to boost the transaction.
+ Unable to increase the fee any further. Otherwise, it will exceed half the current input balance
Your transaction may settle faster if you include an additional network fee. Set your custom fee below.
Your transaction may settle faster if you include an additional network fee. Here is a recommendation:
Use Suggested Fee
diff --git a/app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt b/app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt
index 3f2169582..e79fa9b05 100644
--- a/app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt
+++ b/app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt
@@ -12,7 +12,6 @@ import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.android.testing.UninstallModules
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.runBlocking
import org.junit.After
@@ -41,10 +40,8 @@ import to.bitkit.CurrentActivity
import to.bitkit.R
import to.bitkit.data.AppCacheData
import to.bitkit.data.CacheStore
-import to.bitkit.di.BgDispatcher
+import to.bitkit.di.DbModule
import to.bitkit.di.DispatchersModule
-import to.bitkit.di.IoDispatcher
-import to.bitkit.di.UiDispatcher
import to.bitkit.domain.commands.NotifyPaymentReceived
import to.bitkit.domain.commands.NotifyPaymentReceivedHandler
import to.bitkit.models.NewTransactionSheetDetails
@@ -57,58 +54,35 @@ import to.bitkit.services.NodeEventHandler
import to.bitkit.test.BaseUnitTest
@HiltAndroidTest
-@UninstallModules(DispatchersModule::class)
-@Config(application = HiltTestApplication::class)
+@UninstallModules(DispatchersModule::class, DbModule::class)
+@Config(application = HiltTestApplication::class, sdk = [34]) // Pin Robolectric to an SDK that supports Java 17
@RunWith(RobolectricTestRunner::class)
class LightningNodeServiceTest : BaseUnitTest() {
- @get:Rule(order = 0)
- val mainDispatcherRule = coroutinesTestRule
-
@get:Rule(order = 1)
var hiltRule = HiltAndroidRule(this)
@BindValue
- @JvmField
- val lightningRepo: LightningRepo = mock()
-
- @BindValue
- @JvmField
- val walletRepo: WalletRepo = mock()
-
- @BindValue
- @JvmField
- val notifyPaymentReceivedHandler: NotifyPaymentReceivedHandler = mock()
-
- @BindValue
- @JvmField
- val cacheStore: CacheStore = mock()
+ val lightningRepo = mock()
@BindValue
- @UiDispatcher
- @JvmField
- val uiDispatcher: CoroutineDispatcher = testDispatcher
+ val walletRepo = mock()
@BindValue
- @BgDispatcher
- @JvmField
- val bgDispatcher: CoroutineDispatcher = testDispatcher
+ val notifyPaymentReceivedHandler = mock()
@BindValue
- @IoDispatcher
- @JvmField
- val ioDispatcher: CoroutineDispatcher = testDispatcher
+ val cacheStore = mock()
- private val eventHandlerCaptor: KArgumentCaptor = argumentCaptor()
+ private val captor: KArgumentCaptor = argumentCaptor()
private val cacheDataFlow = MutableSharedFlow(replay = 1)
private val context = ApplicationProvider.getApplicationContext()
@Before
fun setUp() = runBlocking {
hiltRule.inject()
- whenever(
- lightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), eventHandlerCaptor.capture())
- ).thenReturn(Result.success(Unit))
+ whenever(lightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), captor.capture()))
+ .thenReturn(Result.success(Unit))
whenever(lightningRepo.stop()).thenReturn(Result.success(Unit))
// Set up CacheStore mock
@@ -125,9 +99,8 @@ class LightningNodeServiceTest : BaseUnitTest() {
title = context.getString(R.string.notification_received_title),
body = "Received ₿ 100 ($0.10)",
)
- whenever(notifyPaymentReceivedHandler.invoke(any())).thenReturn(
- Result.success(NotifyPaymentReceived.Result.ShowNotification(sheet, notification))
- )
+ whenever(notifyPaymentReceivedHandler.invoke(any()))
+ .thenReturn(Result.success(NotifyPaymentReceived.Result.ShowNotification(sheet, notification)))
// Grant permissions for notifications
val app = context as Application
@@ -148,7 +121,7 @@ class LightningNodeServiceTest : BaseUnitTest() {
controller.create().startCommand(0, 0)
testScheduler.advanceUntilIdle()
- val capturedHandler = eventHandlerCaptor.lastValue
+ val capturedHandler = captor.lastValue
assertNotNull("Event handler should be captured", capturedHandler)
val event = Event.PaymentReceived(
@@ -195,7 +168,7 @@ class LightningNodeServiceTest : BaseUnitTest() {
customRecords = emptyList()
)
- eventHandlerCaptor.lastValue?.invoke(event)
+ captor.lastValue?.invoke(event)
testScheduler.advanceUntilIdle()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -223,7 +196,7 @@ class LightningNodeServiceTest : BaseUnitTest() {
customRecords = emptyList()
)
- eventHandlerCaptor.lastValue?.invoke(event)
+ captor.lastValue?.invoke(event)
testScheduler.advanceUntilIdle()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -235,6 +208,6 @@ class LightningNodeServiceTest : BaseUnitTest() {
assertNotNull("Payment notification should be present", paymentNotification)
val body = paymentNotification?.extras?.getString(Notification.EXTRA_TEXT)
- assertEquals("Received ₿ 100 (\$0.10)", body)
+ assertEquals($$"Received ₿ 100 ($0.10)", body)
}
}
diff --git a/app/src/test/java/to/bitkit/di/TestModule.kt b/app/src/test/java/to/bitkit/di/TestModule.kt
new file mode 100644
index 000000000..4e97cc2a8
--- /dev/null
+++ b/app/src/test/java/to/bitkit/di/TestModule.kt
@@ -0,0 +1,56 @@
+package to.bitkit.di
+
+import android.content.Context
+import androidx.room.Room
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import dagger.hilt.testing.TestInstallIn
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import to.bitkit.data.AppDb
+import to.bitkit.data.dao.TransferDao
+import javax.inject.Singleton
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Module
+@TestInstallIn(
+ components = [SingletonComponent::class],
+ replaces = [DispatchersModule::class, DbModule::class]
+)
+object TestModule {
+
+ @Provides
+ @Singleton
+ @UiDispatcher
+ fun provideUiDispatcher(): CoroutineDispatcher = UnconfinedTestDispatcher()
+
+ @Provides
+ @Singleton
+ @IoDispatcher
+ fun provideIoDispatcher(): CoroutineDispatcher = UnconfinedTestDispatcher()
+
+ @Provides
+ @Singleton
+ @BgDispatcher
+ fun provideBgDispatcher(): CoroutineDispatcher = UnconfinedTestDispatcher()
+
+ @Provides
+ @Singleton
+ fun provideAppDb(
+ @ApplicationContext context: Context,
+ ): AppDb {
+ return Room.inMemoryDatabaseBuilder(
+ context,
+ AppDb::class.java
+ )
+ .allowMainThreadQueries()
+ .build()
+ }
+
+ @Provides
+ @Singleton
+ fun provideTransferDao(db: AppDb): TransferDao = db.transferDao()
+}
diff --git a/app/src/test/java/to/bitkit/ext/DateTimeExtTest.kt b/app/src/test/java/to/bitkit/ext/DateTimeExtTest.kt
index 3fcd5408e..f748a3ee7 100644
--- a/app/src/test/java/to/bitkit/ext/DateTimeExtTest.kt
+++ b/app/src/test/java/to/bitkit/ext/DateTimeExtTest.kt
@@ -8,7 +8,9 @@ import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class DateTimeExtTest : BaseUnitTest() {
@Test
diff --git a/app/src/test/java/to/bitkit/repositories/ActivityDetailViewModelTest.kt b/app/src/test/java/to/bitkit/repositories/ActivityDetailViewModelTest.kt
index 5fb886635..f2d301e85 100644
--- a/app/src/test/java/to/bitkit/repositories/ActivityDetailViewModelTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/ActivityDetailViewModelTest.kt
@@ -10,9 +10,11 @@ import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
+import org.mockito.kotlin.mockingDetails
import org.mockito.kotlin.whenever
import to.bitkit.R
import to.bitkit.data.SettingsStore
+import to.bitkit.ext.create
import to.bitkit.test.BaseUnitTest
import to.bitkit.viewmodels.ActivityDetailViewModel
import kotlin.test.assertEquals
@@ -20,6 +22,7 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class ActivityDetailViewModelTest : BaseUnitTest() {
+ private lateinit var sut: ActivityDetailViewModel
private val context = mock()
private val activityRepo = mock()
@@ -27,14 +30,15 @@ class ActivityDetailViewModelTest : BaseUnitTest() {
private val settingsStore = mock()
private val lightningRepo = mock()
- private lateinit var sut: ActivityDetailViewModel
+ companion object Fixtures {
+ const val ACTIVITY_ID = "test-activity-1"
+ const val ORDER_ID = "test-order-id"
+ }
@Before
fun setUp() {
- whenever(context.getString(R.string.wallet__activity_error_not_found))
- .thenReturn("Activity not found")
- whenever(context.getString(R.string.wallet__activity_error_load_failed))
- .thenReturn("Failed to load activity")
+ whenever(context.getString(R.string.wallet__activity_error_not_found)).thenReturn("Activity not found")
+ whenever(context.getString(R.string.wallet__activity_error_load_failed)).thenReturn("Failed to load activity")
whenever(blocktankRepo.blocktankState).thenReturn(MutableStateFlow(BlocktankState()))
whenever(activityRepo.activitiesChanged).thenReturn(MutableStateFlow(System.currentTimeMillis()))
@@ -57,41 +61,27 @@ class ActivityDetailViewModelTest : BaseUnitTest() {
@Test
fun `findOrderForTransfer finds order by channelId`() = test {
- val orderId = "test-order-id"
- val mockOrder = mock {
- on { id } doReturn orderId
- }
-
- whenever(blocktankRepo.blocktankState).thenReturn(
- MutableStateFlow(BlocktankState(orders = listOf(mockOrder)))
- )
+ val order = mock { on { id } doReturn ORDER_ID }
+ whenever(blocktankRepo.blocktankState).thenReturn(MutableStateFlow(BlocktankState(orders = listOf(order))))
- val result = sut.findOrderForTransfer(orderId, null)
+ val result = sut.findOrderForTransfer(ORDER_ID, null)
- assertEquals(mockOrder, result)
+ assertEquals(order, result)
}
@Test
fun `findOrderForTransfer finds order by channelId matching order id`() = test {
- val orderId = "order-123"
- val mockOrder = mock {
- on { id } doReturn orderId
- }
+ val order = mock { on { id } doReturn ORDER_ID }
+ whenever(blocktankRepo.blocktankState).thenReturn(MutableStateFlow(BlocktankState(orders = listOf(order))))
- whenever(blocktankRepo.blocktankState).thenReturn(
- MutableStateFlow(BlocktankState(orders = listOf(mockOrder)))
- )
-
- val result = sut.findOrderForTransfer(orderId, null)
+ val result = sut.findOrderForTransfer(ORDER_ID, null)
- assertEquals(mockOrder, result)
+ assertEquals(order, result)
}
@Test
fun `findOrderForTransfer returns null when order not found`() = test {
- whenever(blocktankRepo.blocktankState).thenReturn(
- MutableStateFlow(BlocktankState(orders = emptyList()))
- )
+ whenever(blocktankRepo.blocktankState).thenReturn(MutableStateFlow(BlocktankState(orders = emptyList())))
val result = sut.findOrderForTransfer("non-existent-id", null)
@@ -100,97 +90,83 @@ class ActivityDetailViewModelTest : BaseUnitTest() {
@Test
fun `loadActivity starts observation of activity changes`() = test {
- val activityId = "test-activity-1"
- val initialActivity = createTestActivity(activityId, confirmed = false)
- val updatedActivity = createTestActivity(activityId, confirmed = true)
+ val initialActivity = createTestActivity(ACTIVITY_ID, confirmed = false)
+ val updatedActivity = createTestActivity(ACTIVITY_ID, confirmed = true)
val activitiesChangedFlow = MutableStateFlow(System.currentTimeMillis())
whenever(activityRepo.activitiesChanged).thenReturn(activitiesChangedFlow)
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.success(initialActivity))
- whenever(activityRepo.getActivityTags(activityId))
- .thenReturn(Result.success(emptyList()))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.success(initialActivity))
+ whenever(activityRepo.getActivityTags(ACTIVITY_ID)).thenReturn(Result.success(emptyList()))
// Load activity
- sut.loadActivity(activityId)
+ sut.loadActivity(ACTIVITY_ID)
// Verify initial state loaded
val initialState = sut.uiState.value.activityLoadState
assertTrue(initialState is ActivityDetailViewModel.ActivityLoadState.Success)
- assertEquals(initialActivity, (initialState as ActivityDetailViewModel.ActivityLoadState.Success).activity)
+ assertEquals(initialActivity, initialState.activity)
// Simulate activity update
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.success(updatedActivity))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.success(updatedActivity))
activitiesChangedFlow.value = System.currentTimeMillis()
// Verify ViewModel reflects updated activity
val updatedState = sut.uiState.value.activityLoadState
assertTrue(updatedState is ActivityDetailViewModel.ActivityLoadState.Success)
- assertEquals(updatedActivity, (updatedState as ActivityDetailViewModel.ActivityLoadState.Success).activity)
+ assertEquals(updatedActivity, updatedState.activity)
}
@Test
fun `clearActivityState stops observation`() = test {
- val activityId = "test-activity-1"
- val activity = createTestActivity(activityId)
+ val activity = createTestActivity(ACTIVITY_ID)
val activitiesChangedFlow = MutableStateFlow(System.currentTimeMillis())
whenever(activityRepo.activitiesChanged).thenReturn(activitiesChangedFlow)
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.success(activity))
- whenever(activityRepo.getActivityTags(activityId))
- .thenReturn(Result.success(emptyList()))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.success(activity))
+ whenever(activityRepo.getActivityTags(ACTIVITY_ID)).thenReturn(Result.success(emptyList()))
// Load activity
- sut.loadActivity(activityId)
+ sut.loadActivity(ACTIVITY_ID)
// Clear state
sut.clearActivityState()
// Trigger activity change
- val callCountBefore = org.mockito.kotlin.mockingDetails(activityRepo).invocations.size
+ val callCountBefore = mockingDetails(activityRepo).invocations.size
activitiesChangedFlow.value = System.currentTimeMillis()
// Verify no reload after clear (getActivity not called again)
- val callCountAfter = org.mockito.kotlin.mockingDetails(activityRepo).invocations.size
+ val callCountAfter = mockingDetails(activityRepo).invocations.size
assertEquals(callCountBefore, callCountAfter)
}
@Test
fun `reloadActivity keeps last state on failure`() = test {
- val activityId = "test-activity-1"
- val activity = createTestActivity(activityId)
+ val activity = createTestActivity(ACTIVITY_ID)
val activitiesChangedFlow = MutableStateFlow(System.currentTimeMillis())
whenever(activityRepo.activitiesChanged).thenReturn(activitiesChangedFlow)
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.success(activity))
- whenever(activityRepo.getActivityTags(activityId))
- .thenReturn(Result.success(emptyList()))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.success(activity))
+ whenever(activityRepo.getActivityTags(ACTIVITY_ID)).thenReturn(Result.success(emptyList()))
// Load activity
- sut.loadActivity(activityId)
+ sut.loadActivity(ACTIVITY_ID)
// Simulate reload failure
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.failure(Exception("Network error")))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.failure(Exception("Network error")))
activitiesChangedFlow.value = System.currentTimeMillis()
// Verify last known state is preserved
val state = sut.uiState.value.activityLoadState
assertTrue(state is ActivityDetailViewModel.ActivityLoadState.Success)
- assertEquals(activity, (state as ActivityDetailViewModel.ActivityLoadState.Success).activity)
+ assertEquals(activity, state.activity)
}
@Test
fun `loadActivity handles error gracefully`() = test {
- val activityId = "test-activity-1"
-
- whenever(activityRepo.getActivity(activityId))
- .thenReturn(Result.failure(Exception("Database error")))
+ whenever(activityRepo.getActivity(ACTIVITY_ID)).thenReturn(Result.failure(Exception("Database error")))
- sut.loadActivity(activityId)
+ sut.loadActivity(ACTIVITY_ID)
val state = sut.uiState.value.activityLoadState
assertTrue(state is ActivityDetailViewModel.ActivityLoadState.Error)
@@ -201,7 +177,7 @@ class ActivityDetailViewModelTest : BaseUnitTest() {
confirmed: Boolean = false,
): Activity.Onchain {
return Activity.Onchain(
- v1 = OnchainActivity(
+ v1 = OnchainActivity.create(
id = id,
txType = PaymentType.RECEIVED,
txId = "tx-$id",
@@ -211,15 +187,7 @@ class ActivityDetailViewModelTest : BaseUnitTest() {
address = "bc1...",
confirmed = confirmed,
timestamp = (System.currentTimeMillis() / 1000).toULong(),
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
confirmTimestamp = if (confirmed) (System.currentTimeMillis() / 1000).toULong() else null,
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null,
)
)
}
diff --git a/app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt b/app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt
index 87fb93a98..a6249d3cd 100644
--- a/app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt
@@ -8,7 +8,6 @@ import com.synonym.bitkitcore.PaymentType
import com.synonym.bitkitcore.SortDirection
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.datetime.Clock
import org.junit.Before
import org.junit.Test
import org.lightningdevkit.ldknode.PaymentDetails
@@ -25,14 +24,18 @@ import org.mockito.kotlin.wheneverBlocking
import to.bitkit.data.AppCacheData
import to.bitkit.data.CacheStore
import to.bitkit.data.dto.PendingBoostActivity
+import to.bitkit.ext.create
import to.bitkit.services.CoreService
import to.bitkit.test.BaseUnitTest
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
@Suppress("LargeClass")
+@OptIn(ExperimentalTime::class)
class ActivityRepoTest : BaseUnitTest() {
private val coreService = mock()
@@ -56,25 +59,15 @@ class ActivityRepoTest : BaseUnitTest() {
on { v1 } doReturn testActivityV1
}
- private val baseOnchainActivity = OnchainActivity(
+ private val baseOnchainActivity = OnchainActivity.create(
id = "base_activity_id",
txType = PaymentType.SENT,
txId = "base_tx_id",
value = 1000uL,
fee = 100uL,
- feeRate = 10uL,
address = "bc1test",
- confirmed = false,
timestamp = 1234567890uL,
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
- confirmTimestamp = null,
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null
+ feeRate = 10uL,
)
@Suppress("LongParameterList")
@@ -140,7 +133,7 @@ class ActivityRepoTest : BaseUnitTest() {
}
private fun setupSyncActivitiesMocks(
- cacheData: AppCacheData
+ cacheData: AppCacheData,
) {
whenever(cacheStore.data).thenReturn(flowOf(cacheData))
wheneverBlocking { lightningRepo.getPayments() }.thenReturn(Result.success(emptyList()))
diff --git a/app/src/test/java/to/bitkit/repositories/CurrencyRepoTest.kt b/app/src/test/java/to/bitkit/repositories/CurrencyRepoTest.kt
index 2faadc35b..8cbdcdf19 100644
--- a/app/src/test/java/to/bitkit/repositories/CurrencyRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/CurrencyRepoTest.kt
@@ -3,7 +3,6 @@ package to.bitkit.repositories
import app.cash.turbine.test
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.take
-import kotlinx.datetime.Clock
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
@@ -18,24 +17,26 @@ import to.bitkit.models.FxRate
import to.bitkit.models.PrimaryDisplay
import to.bitkit.services.CurrencyService
import to.bitkit.test.BaseUnitTest
-import to.bitkit.ui.shared.toast.ToastEventBus
import java.math.BigDecimal
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class CurrencyRepoTest : BaseUnitTest() {
- private val currencyService: CurrencyService = mock()
- private val settingsStore: SettingsStore = mock()
- private val cacheStore: CacheStore = mock()
- private val toastEventBus: ToastEventBus = mock()
- private val clock: Clock = mock()
+ private val currencyService = mock()
+ private val settingsStore = mock()
+ private val cacheStore = mock()
+ private val clock = mock()
private lateinit var sut: CurrencyRepo
+ @Suppress("SpellCheckingInspection")
private val testRates = listOf(
FxRate(
symbol = "BTCUSD",
@@ -149,9 +150,9 @@ class CurrencyRepoTest : BaseUnitTest() {
awaitItem() // Wait for state to be initialized
assertEquals(testRates[1], rate)
- assertEquals(45000.0, rate?.rate)
- assertEquals("€", rate?.currencySymbol)
- assertEquals("🇪🇺", rate?.currencyFlag)
+ assertEquals(45_000.0, rate.rate)
+ assertEquals("€", rate.currencySymbol)
+ assertEquals("🇪🇺", rate.currencyFlag)
cancelAndIgnoreRemainingEvents()
}
}
diff --git a/app/src/test/java/to/bitkit/repositories/HealthRepoTest.kt b/app/src/test/java/to/bitkit/repositories/HealthRepoTest.kt
index aa4b777f0..f0ecfef10 100644
--- a/app/src/test/java/to/bitkit/repositories/HealthRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/HealthRepoTest.kt
@@ -4,8 +4,6 @@ import app.cash.turbine.test
import com.synonym.bitkitcore.IBtOrder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.datetime.Clock
-import kotlinx.datetime.Instant
import org.junit.Before
import org.junit.Test
import org.lightningdevkit.ldknode.ChannelDetails
@@ -19,13 +17,17 @@ import to.bitkit.models.HealthState
import to.bitkit.models.NodeLifecycleState
import to.bitkit.test.BaseUnitTest
import kotlin.test.assertEquals
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
+@OptIn(ExperimentalTime::class)
class HealthRepoTest : BaseUnitTest() {
- private val connectivityRepo: ConnectivityRepo = mock()
- private val lightningRepo: LightningRepo = mock()
- private val blocktankRepo: BlocktankRepo = mock()
- private val cacheStore: CacheStore = mock()
+ private val connectivityRepo = mock()
+ private val lightningRepo = mock()
+ private val blocktankRepo = mock()
+ private val cacheStore = mock()
private val clock: Clock = mock()
private lateinit var sut: HealthRepo
diff --git a/app/src/test/java/to/bitkit/repositories/PreActivityMetadataRepoTest.kt b/app/src/test/java/to/bitkit/repositories/PreActivityMetadataRepoTest.kt
index c18750972..139405fab 100644
--- a/app/src/test/java/to/bitkit/repositories/PreActivityMetadataRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/PreActivityMetadataRepoTest.kt
@@ -4,8 +4,6 @@ import app.cash.turbine.test
import com.synonym.bitkitcore.PreActivityMetadata
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.datetime.Clock
-import kotlinx.datetime.Instant
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.any
@@ -21,8 +19,11 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTime::class)
class PreActivityMetadataRepoTest : BaseUnitTest() {
private val coreService = mock()
diff --git a/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt b/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt
index 08f63e09d..59ac8dae1 100644
--- a/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt
@@ -6,7 +6,6 @@ import com.synonym.bitkitcore.IBtChannel
import com.synonym.bitkitcore.IBtOrder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.datetime.Clock
import org.junit.Before
import org.junit.Test
import org.lightningdevkit.ldknode.BalanceDetails
@@ -29,7 +28,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class TransferRepoTest : BaseUnitTest() {
private lateinit var sut: TransferRepo
diff --git a/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt b/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt
index 97467c587..89017daf6 100644
--- a/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt
+++ b/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt
@@ -44,6 +44,7 @@ class WalletRepoTest : BaseUnitTest() {
private val preActivityMetadataRepo = mock()
private val deriveBalanceStateUseCase = mock()
private val wipeWalletUseCase = mock()
+ private val transferRepo = mock()
companion object Fixtures {
const val ACTIVITY_TAG = "testTag"
@@ -107,6 +108,7 @@ class WalletRepoTest : BaseUnitTest() {
preActivityMetadataRepo = preActivityMetadataRepo,
deriveBalanceStateUseCase = deriveBalanceStateUseCase,
wipeWalletUseCase = wipeWalletUseCase,
+ transferRepo = transferRepo,
)
@Test
diff --git a/app/src/test/java/to/bitkit/test/BaseUnitTest.kt b/app/src/test/java/to/bitkit/test/BaseUnitTest.kt
index 9368759f8..ef07858b0 100644
--- a/app/src/test/java/to/bitkit/test/BaseUnitTest.kt
+++ b/app/src/test/java/to/bitkit/test/BaseUnitTest.kt
@@ -11,7 +11,7 @@ import org.junit.Rule
abstract class BaseUnitTest(
testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) {
- @get:Rule
+ @get:Rule(order = 0)
val coroutinesTestRule = MainDispatcherRule(testDispatcher)
protected val testDispatcher get() = coroutinesTestRule.testDispatcher
diff --git a/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt b/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt
index dc83fc88a..8afe69d9a 100644
--- a/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt
+++ b/app/src/test/java/to/bitkit/ui/WalletViewModelTest.kt
@@ -20,6 +20,7 @@ import to.bitkit.repositories.BackupRepo
import to.bitkit.repositories.BlocktankRepo
import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.LightningState
+import to.bitkit.repositories.SyncSource
import to.bitkit.repositories.WalletRepo
import to.bitkit.repositories.WalletState
import to.bitkit.test.BaseUnitTest
@@ -28,23 +29,23 @@ import to.bitkit.viewmodels.WalletViewModel
@OptIn(ExperimentalCoroutinesApi::class)
class WalletViewModelTest : BaseUnitTest() {
-
private lateinit var sut: WalletViewModel
- private val walletRepo: WalletRepo = mock()
- private val lightningRepo: LightningRepo = mock()
- private val settingsStore: SettingsStore = mock()
- private val backupRepo: BackupRepo = mock()
- private val blocktankRepo: BlocktankRepo = mock()
- private val mockLightningState = MutableStateFlow(LightningState())
- private val mockWalletState = MutableStateFlow(WalletState())
- private val mockBalanceState = MutableStateFlow(BalanceState())
- private val mockIsRecoveryMode = MutableStateFlow(false)
+ private val walletRepo = mock()
+ private val lightningRepo = mock()
+ private val settingsStore = mock()
+ private val backupRepo = mock()
+ private val blocktankRepo = mock()
+
+ private val lightningState = MutableStateFlow(LightningState())
+ private val walletState = MutableStateFlow(WalletState())
+ private val balanceState = MutableStateFlow(BalanceState())
+ private val isRecoveryMode = MutableStateFlow(false)
@Before
fun setUp() {
- whenever(walletRepo.walletState).thenReturn(mockWalletState)
- whenever(lightningRepo.lightningState).thenReturn(mockLightningState)
+ whenever(walletRepo.walletState).thenReturn(walletState)
+ whenever(lightningRepo.lightningState).thenReturn(lightningState)
sut = WalletViewModel(
bgDispatcher = testDispatcher,
@@ -82,7 +83,7 @@ class WalletViewModelTest : BaseUnitTest() {
fun `onPullToRefresh should sync wallet`() = test {
sut.onPullToRefresh()
- verify(walletRepo).syncNodeAndWallet()
+ verify(walletRepo).syncNodeAndWallet(SyncSource.MANUAL)
}
@Test
@@ -165,7 +166,7 @@ class WalletViewModelTest : BaseUnitTest() {
fun `backup restore should not be triggered when wallet exists while not restoring`() = test {
assertEquals(RestoreState.Initial, sut.restoreState)
- mockWalletState.value = mockWalletState.value.copy(walletExists = true)
+ walletState.value = walletState.value.copy(walletExists = true)
verify(backupRepo, never()).performFullRestoreFromLatestBackup()
}
@@ -173,7 +174,7 @@ class WalletViewModelTest : BaseUnitTest() {
@Test
fun `onBackupRestoreSuccess should reset restoreState`() = test {
whenever(backupRepo.performFullRestoreFromLatestBackup()).thenReturn(Result.success(Unit))
- mockWalletState.value = mockWalletState.value.copy(walletExists = true)
+ walletState.value = walletState.value.copy(walletExists = true)
sut.restoreWallet("mnemonic", "passphrase")
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState)
@@ -187,7 +188,7 @@ class WalletViewModelTest : BaseUnitTest() {
val testError = Exception("Test error")
whenever(backupRepo.performFullRestoreFromLatestBackup()).thenReturn(Result.failure(testError))
sut.restoreWallet("mnemonic", "passphrase")
- mockWalletState.value = mockWalletState.value.copy(walletExists = true)
+ walletState.value = walletState.value.copy(walletExists = true)
assertEquals(RestoreState.Completed, sut.restoreState)
sut.proceedWithoutRestore(onDone = {})
@@ -203,7 +204,7 @@ class WalletViewModelTest : BaseUnitTest() {
sut.restoreWallet("mnemonic", "passphrase")
assertEquals(RestoreState.InProgress.Wallet, sut.restoreState)
- mockWalletState.value = mockWalletState.value.copy(walletExists = true)
+ walletState.value = walletState.value.copy(walletExists = true)
assertEquals(RestoreState.Completed, sut.restoreState)
sut.onRestoreContinue()
@@ -221,10 +222,10 @@ class WalletViewModelTest : BaseUnitTest() {
// Set up mocks BEFORE creating SUT
whenever(testWalletRepo.walletState).thenReturn(testWalletState)
- whenever(testWalletRepo.balanceState).thenReturn(mockBalanceState)
+ whenever(testWalletRepo.balanceState).thenReturn(balanceState)
whenever(testWalletRepo.walletExists()).thenReturn(true)
- whenever(testLightningRepo.lightningState).thenReturn(mockLightningState)
- whenever(testLightningRepo.isRecoveryMode).thenReturn(mockIsRecoveryMode)
+ whenever(testLightningRepo.lightningState).thenReturn(lightningState)
+ whenever(testLightningRepo.isRecoveryMode).thenReturn(isRecoveryMode)
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
.thenReturn(Result.success(Unit))
@@ -258,11 +259,11 @@ class WalletViewModelTest : BaseUnitTest() {
// Set up mocks BEFORE creating SUT
whenever(testWalletRepo.walletState).thenReturn(testWalletState)
- whenever(testWalletRepo.balanceState).thenReturn(mockBalanceState)
+ whenever(testWalletRepo.balanceState).thenReturn(balanceState)
whenever(testWalletRepo.walletExists()).thenReturn(true)
whenever(testWalletRepo.restoreWallet(any(), anyOrNull())).thenReturn(Result.success(Unit))
- whenever(testLightningRepo.lightningState).thenReturn(mockLightningState)
- whenever(testLightningRepo.isRecoveryMode).thenReturn(mockIsRecoveryMode)
+ whenever(testLightningRepo.lightningState).thenReturn(lightningState)
+ whenever(testLightningRepo.isRecoveryMode).thenReturn(isRecoveryMode)
whenever(testLightningRepo.start(any(), anyOrNull(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
.thenReturn(Result.success(Unit))
diff --git a/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt b/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt
index f25be6e2c..7cecba8f0 100644
--- a/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt
+++ b/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt
@@ -15,6 +15,7 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.kotlin.wheneverBlocking
+import to.bitkit.ext.create
import to.bitkit.models.TransactionSpeed
import to.bitkit.repositories.ActivityRepo
import to.bitkit.repositories.LightningRepo
@@ -40,25 +41,15 @@ class BoostTransactionViewModelTest : BaseUnitTest() {
private val testTotalFee = 1000UL
private val testValue = 50000UL
- private val mockOnchainActivity = OnchainActivity(
+ private val mockOnchainActivity = OnchainActivity.create(
id = "test_id",
txType = PaymentType.SENT,
txId = mockTxId,
value = testValue,
fee = 500UL,
- feeRate = 10UL,
address = mockAddress,
- confirmed = false,
timestamp = 1234567890UL,
- isBoosted = false,
- boostTxIds = emptyList(),
- isTransfer = false,
- doesExist = true,
- confirmTimestamp = null,
- channelId = null,
- transferTxId = null,
- createdAt = null,
- updatedAt = null
+ feeRate = 10UL,
)
private val mockActivitySent = Activity.Onchain(v1 = mockOnchainActivity)
diff --git a/app/src/test/java/to/bitkit/viewmodels/AmountInputViewModelTest.kt b/app/src/test/java/to/bitkit/viewmodels/AmountInputViewModelTest.kt
index 2c8bac799..e36008926 100644
--- a/app/src/test/java/to/bitkit/viewmodels/AmountInputViewModelTest.kt
+++ b/app/src/test/java/to/bitkit/viewmodels/AmountInputViewModelTest.kt
@@ -2,7 +2,6 @@ package to.bitkit.viewmodels
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
-import kotlinx.datetime.Clock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
@@ -29,17 +28,22 @@ import to.bitkit.ui.components.KEY_000
import to.bitkit.ui.components.KEY_DECIMAL
import to.bitkit.ui.components.KEY_DELETE
import to.bitkit.ui.components.NumberPadType
+import kotlin.time.Clock
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.ExperimentalTime
@Suppress("LargeClass")
+@OptIn(ExperimentalTime::class)
class AmountInputViewModelTest : BaseUnitTest() {
private lateinit var viewModel: AmountInputViewModel
- private val currencyService: CurrencyService = mock()
- private val settingsStore: SettingsStore = mock()
- private val cacheStore: CacheStore = mock()
- private val clock: Clock = mock()
private lateinit var currencyRepo: CurrencyRepo
+ private val currencyService = mock()
+ private val settingsStore = mock()
+ private val cacheStore = mock()
+ private val clock = mock()
+
+ @Suppress("SpellCheckingInspection")
private val testRates = listOf(
FxRate(
symbol = "BTCUSD",
@@ -65,7 +69,7 @@ class AmountInputViewModelTest : BaseUnitTest() {
settingsStore = settingsStore,
cacheStore = cacheStore,
enablePolling = false,
- clock = clock
+ clock = clock,
)
viewModel = AmountInputViewModel(currencyRepo)
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
index 746b40004..79b0e5414 100644
--- a/config/detekt/detekt.yml
+++ b/config/detekt/detekt.yml
@@ -2,10 +2,10 @@ build:
maxIssues: 0
excludeCorrectable: false
weights:
- # complexity: 2
- # LongParameterList: 1
- # style: 1
- # comments: 1
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
config:
validation: true
@@ -34,11 +34,11 @@ processors:
console-reports:
active: true
exclude:
- - 'ProjectStatisticsReport'
- - 'ComplexityReport'
- - 'NotificationReport'
- - 'FindingsReport'
- - 'FileBasedFindingsReport'
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ - 'FindingsReport'
+ - 'FileBasedFindingsReport'
# - 'LiteFindingsReport'
output-reports:
@@ -67,7 +67,7 @@ comments:
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
KDocReferencesNonPublicProperty:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
OutdatedDocumentation:
active: false
matchTypeParameters: true
@@ -75,7 +75,7 @@ comments:
allowParamOnConstructorProperties: false
UndocumentedPublicClass:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
@@ -84,11 +84,11 @@ comments:
ignoreDefaultCompanionObject: false
UndocumentedPublicFunction:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedFunction: false
UndocumentedPublicProperty:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedProperty: false
complexity:
@@ -123,7 +123,7 @@ complexity:
- 'with'
LabeledExpression:
active: false
- ignoredLabels: []
+ ignoredLabels: [ ]
LargeClass:
active: true
threshold: 600
@@ -138,7 +138,7 @@ complexity:
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
- ignoreAnnotatedParameter: []
+ ignoreAnnotatedParameter: [ ]
ignoreAnnotated:
- 'Composable'
MethodOverloading:
@@ -164,14 +164,14 @@ complexity:
active: false
StringLiteralDuplication:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/ext/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/ext/**' ]
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
@@ -180,7 +180,7 @@ complexity:
ignoreDeprecated: true
ignorePrivate: true
ignoreOverridden: true
- ignoreAnnotatedFunctions: ['Preview']
+ ignoreAnnotatedFunctions: [ 'Preview' ]
coroutines:
active: true
@@ -249,7 +249,7 @@ exceptions:
- 'toString'
InstanceOfCheckForException:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
@@ -275,7 +275,7 @@ exceptions:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
@@ -290,7 +290,7 @@ exceptions:
active: true
TooGenericExceptionCaught:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
@@ -327,7 +327,7 @@ naming:
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
- forbiddenName: []
+ forbiddenName: [ ]
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
@@ -336,7 +336,7 @@ naming:
minimumFunctionNameLength: 3
FunctionNaming:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
ignoreAnnotated:
@@ -407,10 +407,10 @@ performance:
threshold: 3
ForEachOnRange:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
SpreadOperator:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnnecessaryPartOfBinaryExpression:
active: false
UnnecessaryTemporaryInstantiation:
@@ -443,7 +443,7 @@ potential-bugs:
- 'java.util.HashMap'
ElseCaseInsteadOfExhaustiveWhen:
active: false
- ignoredSubjectTypes: []
+ ignoredSubjectTypes: [ ]
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
@@ -469,7 +469,7 @@ potential-bugs:
- 'kotlin.sequences.Sequence'
- 'kotlinx.coroutines.flow.*Flow'
- 'java.util.stream.*Stream'
- ignoreFunctionCall: []
+ ignoreFunctionCall: [ ]
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
@@ -483,13 +483,13 @@ potential-bugs:
active: true
LateinitUsage:
active: false
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: true
MissingPackageDeclaration:
active: false
- excludes: ['**/*.kts']
+ excludes: [ '**/*.kts' ]
NullCheckOnMutableProperty:
active: false
NullableToStringCall:
@@ -510,7 +510,7 @@ potential-bugs:
active: true
UnsafeCallOnNullableType:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnsafeCast:
active: true
UnusedUnaryOperator:
@@ -601,7 +601,7 @@ style:
allowedPatterns: ''
ForbiddenImport:
active: false
- imports: []
+ imports: [ ]
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
@@ -612,7 +612,7 @@ style:
value: 'kotlin.io.println'
ForbiddenSuppress:
active: false
- rules: []
+ rules: [ ]
ForbiddenVoid:
active: true
ignoreOverridden: false
@@ -621,20 +621,14 @@ style:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
- excludedFunctions: []
+ excludedFunctions: [ ]
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: true
- excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
- ignoreNumbers:
- - '-1'
- - '0'
- - '0.25'
- - '0.5'
- - '1'
- - '2'
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ]
+ ignoreNumbers: [ '-1', '0', '0.25', '0.5', '1', '2', '3', '4', '5', '10', '25', '50', '100', '250', '500', '1000', '2500', '5000', '10000', '25000', '50000', '100000' ]
ignoreHashCodeFunction: true
ignorePropertyDeclaration: true
ignoreLocalVariableDeclaration: true
@@ -710,7 +704,7 @@ style:
StringShouldBeRawString:
active: false
maxEscapedCharacterCount: 2
- ignoredCharacters: []
+ ignoredCharacters: [ ]
ThrowsCount:
active: true
max: 2
@@ -1080,42 +1074,89 @@ Compose:
active: true
ComposableNaming:
active: true
+ # -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
+ # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
ComposableParamOrder:
active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
CompositionLocalAllowlist:
active: true
+ # -- You can optionally define a list of CompositionLocals that are allowed here
+ # allowedCompositionLocals: LocalSomething,LocalSomethingElse
CompositionLocalNaming:
active: true
ContentEmitterReturningValues:
active: true
- ContentSlotReused:
- active: true
+ # -- You can optionally add your own composables here
+ # contentEmitters: MyComposable,MyOtherComposable
ContentTrailingLambda:
active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
+ # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically).
+ # -- The difference with treatAsLambda is that those need `@Composable` MyLambdaType in the definition, while these won't.
+ # treatAsComposableLambda: MyComposableLambdaType
+ ContentSlotReused:
+ active: true
+ # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically).
+ # -- The difference with treatAsLambda is that those need `@Composable` MyLambdaType in the definition, while these won't.
+ # treatAsComposableLambda: MyComposableLambdaType
DefaultsVisibility:
active: true
LambdaParameterEventTrailing:
active: true
+ # -- You can optionally add your own composables here
+ # contentEmitters: MyComposable,MyOtherComposable
+ # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals)
+ # contentEmittersDenylist: MyNonEmitterComposable
LambdaParameterInRestartableEffect:
active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
Material2:
- active: false
+ active: false # Opt-in, disabled by default. Turn on if you want to disallow Material 2 usages.
+ # -- You can optionally allow parts of it, if you are in the middle of a migration.
+ # allowedFromM2: icons.Icons,TopAppBar
ModifierClickableOrder:
active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
ModifierComposed:
active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
ModifierMissing:
active: true
+ # -- You can optionally control the visibility of which composables to check for here
+ # -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`)
+ # checkModifiersForVisibility: only_public
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ # -- You can suppress this check in functions annotated with these annotations
+ # ignoreAnnotated: ['Potato', 'Banana']
ModifierNaming:
active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
ModifierNotUsedAtRoot:
active: true
+ # -- You can optionally add your own composables here
+ # contentEmitters: MyComposable,MyOtherComposable
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
ModifierReused:
active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
ModifierWithoutDefault:
active: true
MultipleEmitters:
active: true
+ # -- You can optionally add your own composables here that will count as content emitters
+ # contentEmitters: MyComposable,MyOtherComposable
+ # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals)
+ # contentEmittersDenylist: MyNonEmitterComposable
MutableParams:
active: true
MutableStateAutoboxing:
@@ -1124,19 +1165,34 @@ Compose:
active: true
ParameterNaming:
active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
+ # -- You can optionally add your allowed lambda names (e.g., usages from the past found in the official Compose code)
+ # allowedLambdaParameterNames: onSizeChanged, onGloballyPositioned
PreviewAnnotationNaming:
active: true
PreviewNaming:
- active: false
+ active: false # Opt-in, disabled by default.
+ # -- You can optionally configure the naming strategy for previews.
+ # -- Possible values are: `suffix`, `prefix`, `anywhere`. By default, it will be `suffix`.
+ # previewNamingStrategy: suffix
PreviewPublic:
active: true
- RememberContentMissing:
- active: true
RememberMissing:
active: true
+ RememberContentMissing:
+ active: true
UnstableCollections:
- active: false
+ active: false # Opt-in, disabled by default. Turn on if you want to enforce this (e.g. you have strong skipping disabled)
ViewModelForwarding:
- active: false
+ active: true
+ # -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here:
+ # allowedStateHolderNames: .*ViewModel,.*Presenter
+ # -- You can optionally add an allowlist for Composable names that won't be affected by this rule
+ # allowedForwarding: .*Content,.*FancyStuff
+ # -- You can optionally add an allowlist for ViewModel/StateHolder names that won't be affected by this rule
+ # allowedForwardingOfTypes: PotatoViewModel,(Apple|Banana)ViewModel,.*FancyViewModel
ViewModelInjection:
active: true
+ # -- You can optionally add your own ViewModel factories here
+ # viewModelFactories: hiltViewModel,potatoViewModel
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 967907178..9342bafe0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,55 +1,31 @@
[versions]
-accompanistPermissions = "0.36.0"
-activityCompose = "1.10.1"
-agp = "8.13.0"
-appcompat = "1.7.0"
-barcodeScanning = "17.3.0"
-biometric = "1.4.0-alpha02"
-bouncyCastle = "1.79"
-camera = "1.4.2"
-composeBom = "2025.09.00" # https://developer.android.com/develop/ui/compose/bom/bom-mapping
-constraintlayoutCompose = "1.1.1"
-coreKtx = "1.15.0"
-coreSplashscreen = "1.0.1"
-datastorePrefs = "1.1.4"
+agp = "8.12.0"
+camera = "1.5.2"
detekt = "1.23.8"
-espressoCore = "3.6.1"
-hilt = "2.55"
-hiltAndroidx = "1.2.0"
-junit = "4.13.2"
-junitExt = "1.2.1"
-kotlin = "2.1.10"
-kotlinxDatetime = "0.6.2"
-ksp = "2.1.10-1.0.31"
-ktor = "3.1.1"
-lifecycle = "2.8.7"
-material = "1.12.0"
-mockitoKotlin = "5.4.0"
-navCompose = "2.8.9"
-robolectric = "4.14.1"
-room = "2.6.1"
+hilt = "2.57.2"
+hiltAndroidx = "1.3.0"
+kotlin = "2.2.21"
+ktor = "3.3.3"
+lifecycle = "2.10.0"
+navCompose = "2.9.6"
+room = "2.8.4"
slf4j = "2.0.17"
-testAndroidx = "1.6.1"
-turbine = "1.0.0"
-workRuntimeKtx = "2.10.0"
-zxing = "3.5.2"
-lottieVersion = "6.6.4"
-composeChartsVersion = "0.1.7"
-haze = "1.6.9"
+haze = "1.7.1"
[libraries]
-accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanistPermissions" }
-accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
-bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.31" }
-bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncyCastle" }
+accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version = "0.36.0" }
+accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version = "0.37.3" }
+activity-compose = { module = "androidx.activity:activity-compose", version = "1.12.1" }
+appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
+barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version = "17.3.0" }
+biometric = { module = "androidx.biometric:biometric", version = "1.4.0-alpha04" }
+bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.33" }
+bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.83" }
camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
camera-view = { module = "androidx.camera:camera-view", version.ref = "camera" }
-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+# https://developer.android.com/develop/ui/compose/bom/bom-mapping
+compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2025.12.00" }
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
@@ -58,13 +34,13 @@ compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutCompose" }
-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePrefs" }
+constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version = "1.1.1" }
+core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" }
+core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version = "1.2.0" }
+datastore-preferences = { module = "androidx.datastore:datastore-preferences", version = "1.2.0" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
-detekt-compose-rules = { module = "io.nlopez.compose.rules:detekt", version = "0.4.23" }
-firebase-bom = { module = "com.google.firebase:firebase-bom", version = "34.2.0" }
+detekt-compose-rules = { module = "io.nlopez.compose.rules:detekt", version = "0.5.1" }
+firebase-bom = { module = "com.google.firebase:firebase-bom", version = "34.6.0" }
firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
@@ -72,25 +48,23 @@ hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", vers
hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltAndroidx" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltAndroidx" }
hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltAndroidx" }
-jna = { module = "net.java.dev.jna:jna", version = "5.17.0" }
+jna = { module = "net.java.dev.jna:jna", version = "5.18.1" }
kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
+kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.7.1" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
-#ldk-node-android = { module = "org.lightningdevkit:ldk-node-android", version = "0.7.0" } # upstream
-#ldk-node-android = { module = "org.lightningdevkit:ldk-node-android", version = "0.7.0" } # local
-ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.7.0" } # fork
+ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.7.0-rc.1" } # fork | local: remove `v`
lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" }
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" }
-material = { module = "com.google.android.material:material", version.ref = "material" }
+material = { module = "com.google.android.material:material", version = "1.13.0" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navCompose" }
navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navCompose" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
@@ -99,19 +73,19 @@ room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "
room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
-test-core = { module = "androidx.test:core", version.ref = "testAndroidx" }
+test-core = { module = "androidx.test:core", version = "1.7.0" }
test-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test" }
-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
-test-junit = { group = "junit", name = "junit", version.ref = "junit" }
-test-junit-ext = { module = "androidx.test.ext:junit", version.ref = "junitExt" }
-test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" }
-test-robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
-test-turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
-vss = { module = "com.synonym:vss-client-android", version = "0.3.0" }
-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
-zxing = { module = "com.google.zxing:core", version.ref = "zxing" }
-lottie = { module = "com.airbnb.android:lottie-compose", version.ref = "lottieVersion" }
-charts = { module = "io.github.ehsannarmani:compose-charts", version.ref = "composeChartsVersion" }
+test-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.7.0" }
+test-junit = { group = "junit", name = "junit", version = "4.13.2" }
+test-junit-ext = { module = "androidx.test.ext:junit", version = "1.3.0" }
+test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.1.0" }
+test-robolectric = { module = "org.robolectric:robolectric", version = "4.16" }
+test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.1" }
+vss-client = { module = "com.synonym:vss-client-android", version = "0.4.0" }
+work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version = "2.11.0" }
+zxing = { module = "com.google.zxing:core", version = "3.5.4" }
+lottie = { module = "com.airbnb.android:lottie-compose", version = "6.7.1" }
+charts = { module = "io.github.ehsannarmani:compose-charts", version = "0.2.0" }
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
@@ -119,9 +93,9 @@ haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref =
android-application = { id = "com.android.application", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
-google-services = { id = "com.google.gms.google-services", version = "4.4.3" }
+google-services = { id = "com.google.gms.google-services", version = "4.4.4" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+ksp = { id = "com.google.devtools.ksp", version = "2.3.2" }
room = { id = "androidx.room", version.ref = "room" }