Skip to content

Commit 635084f

Browse files
committed
feat: add calculator v61 app & os widget
fix: simplify scan button highlight fix: keep tab bar fully opaque fix: keep tab bar opaque on exit fix: sync calculator tab bar transition fix: align fiat placeholder text fix: place cursor before placeholder fix: shrink calculator widget preview rows fix: show fiat decimal placeholder chore: fix naming agent rules fix: match calculator cursor style fix: show calculator input cursor fix: animate tab bar visibility refactor: apply naming agent rules refactor: isolate calculator numpad state fix: open app from calculator widget fix: flash calculator numpad errors refactor: extract private calculator numpad chore: update detekt command in docs fix: calculator numberpad bottom see-through refactor: extract MoneyType model fix: highlight calculator input bg instead of border fix: animate calculator pad collapse fix: remove calculator pad divider fix: center calculator os widget fix: refine calculator gestures fix: polish calculator input fix: dismiss calculator on outside tap fix: open calculator from os widget fix: align calculator preview color fix: group calculator fiat display fix: lock calculator widget scroll fix: hide tab bar during calculator input fix: normalize calculator fiat input fix: avoid grouped calculator fiat fix: use calculator number pad fix: show calculator widget preview fix: handle calculator keyboard fix: use calculator input masks
1 parent b176b92 commit 635084f

40 files changed

Lines changed: 2390 additions & 984 deletions

AGENTS.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ GEO=false E2E=true ./gradlew assembleDevRelease
3434
E2E=true E2E_BACKEND=network ./gradlew assembleTnetRelease
3535

3636
# Lint using detekt
37-
./gradlew detekt
37+
./gradlew detekt --rerun-tasks
3838

3939
# Auto-format using detekt
4040
./gradlew detekt --auto-correct
@@ -133,6 +133,13 @@ suspend fun getData() = withContext(Dispatchers.IO) { }
133133

134134
## Common Patterns
135135

136+
### Naming: Use Context, Avoid Redundant Prefixes
137+
-
138+
- Infer names from the surrounding context: file name, component/class name, enclosing function, module, and call site.
139+
- Avoid repeating domain prefixes or suffixes that are already obvious from the context, especially for private, nested, local and file-local symbols, i.e., inner-scoped parts invisible outside their context.
140+
- Only add a prefix/suffix when it resolves real ambiguity at the call site. Preserve existing concise names unless there is a concrete readability or correctness reason to rename them.
141+
- Examples: `Numpad` or `formatAmount`, instead of `CalculatorNumpad` or `formatCalculatorAmount`.
142+
136143
### ViewModel State
137144

138145
```kotlin
@@ -141,7 +148,7 @@ val uiState: StateFlow<UiState> = _uiState.asStateFlow()
141148

142149
fun updateState(action: Action) {
143150
viewModelScope.launch {
144-
_uiState.update { it.copy(/* fields */) }
151+
_uiState.update { it.copy(/**/) }
145152
}
146153
}
147154
```
@@ -160,7 +167,6 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
160167

161168
### Rules
162169

163-
- USE coding rules from `.cursor/default.rules.mdc`
164170
- ALWAYS run `./gradlew compileDevDebugKotlin` after code changes to verify code compiles
165171
- ALWAYS run `./gradlew testDevDebugUnitTest` after code changes to verify tests succeed and fix accordingly
166172
- ALWAYS run `./gradlew detekt` after code changes to check for new lint issues and fix accordingly
@@ -201,7 +207,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
201207
- ALWAYS add trailing commas in multi-line declarations, EXCEPT after a `modifier = ...` last argument — never add a trailing comma there, whether the modifier is a single call (`modifier = Modifier.weight(1f)`) or a chain (`modifier = Modifier.fillMaxWidth().testTag("foo")`)
202208
- ALWAYS use `navController.navigateTo(route)` for simple navigation; NEVER use raw `navController.navigate(route)``navigateTo` prevents duplicate destinations
203209
- ALWAYS prefer `VerticalSpacer`, `HorizontalSpacer`, `FillHeight` and `FillWidth` over `Spacer` when applicable
204-
- PREFER declaring small dependant classes, constants, interfaces or top-level functions in the same file with the core class where these are used
210+
- PREFER declaring small dependant classes, constants, interfaces, or top-level functions in the same file with the core class where these are used
205211
- ALWAYS create data classes for state AFTER viewModel class in same file
206212
- ALWAYS return early where applicable, PREFER guard-like `if` conditions like `if (condition) return`
207213
- USE `docs/` as target dir of saved files when asked to create documentation for new features
@@ -278,3 +284,6 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
278284
- Use `WakeNodeWorker` to manage the handling of remote notifications received via cloud messages
279285
- Use `*Services` to wrap rust library code exposed via bindings
280286
- Use CQRS pattern of Command + Handler like it's done in the `NotifyPaymentReceived` + `NotifyPaymentReceivedHandler` setup
287+
288+
### Other Sources
289+
- Use coding rules from `.cursor/default.rules.mdc`

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The following IDE plugins are recommended for development with Android Studio or
5757

5858
**Commands**
5959
```sh
60-
./gradlew detekt # run analysis + formatting check
60+
./gradlew detekt --rerun-tasks # run analysis + formatting check (rerun flag ensures issues are always listed.)
6161
./gradlew detekt --auto-correct # auto-fix formatting issues
6262
```
6363
Reports are generated in: `app/build/reports/detekt/`.

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@
256256
android:resource="@xml/appwidget_info_weather" />
257257
</receiver>
258258

259+
<!-- Calculator Widget -->
260+
<receiver
261+
android:name=".appwidget.ui.calculator.CalculatorGlanceReceiver"
262+
android:exported="true"
263+
android:label="@string/widgets__calculator__name">
264+
<intent-filter>
265+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
266+
</intent-filter>
267+
<meta-data
268+
android:name="android.appwidget.provider"
269+
android:resource="@xml/appwidget_info_calculator" />
270+
</receiver>
271+
259272
</application>
260273

261274
</manifest>

app/src/main/java/to/bitkit/appwidget/AppWidgetPreferencesStore.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import to.bitkit.data.dto.WeatherDTO
1919
import to.bitkit.data.dto.price.GraphPeriod
2020
import to.bitkit.data.dto.price.PriceDTO
2121
import to.bitkit.data.serializers.AppWidgetDataSerializer
22+
import to.bitkit.repositories.CurrencyRepo
23+
import to.bitkit.repositories.WidgetsRepo
2224
import javax.inject.Inject
2325
import javax.inject.Singleton
2426

@@ -32,6 +34,8 @@ private val Context.appWidgetDataStore: DataStore<AppWidgetData> by dataStore(
3234
interface AppWidgetEntryPoint {
3335
fun appWidgetPreferencesStore(): AppWidgetPreferencesStore
3436
fun appWidgetDataRepository(): AppWidgetDataRepository
37+
fun widgetsRepo(): WidgetsRepo
38+
fun currencyRepo(): CurrencyRepo
3539
}
3640

3741
@Singleton

app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import dagger.assisted.AssistedInject
1818
import to.bitkit.appwidget.model.AppWidgetType
1919
import to.bitkit.appwidget.ui.blocks.BlocksGlanceReceiver
2020
import to.bitkit.appwidget.ui.blocks.BlocksGlanceWidget
21+
import to.bitkit.appwidget.ui.calculator.CalculatorGlanceReceiver
22+
import to.bitkit.appwidget.ui.calculator.CalculatorGlanceWidget
2123
import to.bitkit.appwidget.ui.facts.FactsGlanceReceiver
2224
import to.bitkit.appwidget.ui.facts.FactsGlanceWidget
2325
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceReceiver
@@ -74,6 +76,7 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
7476
AppWidgetType.BLOCKS -> BlocksGlanceReceiver::class.java
7577
AppWidgetType.FACTS -> FactsGlanceReceiver::class.java
7678
AppWidgetType.WEATHER -> WeatherGlanceReceiver::class.java
79+
AppWidgetType.CALCULATOR -> CalculatorGlanceReceiver::class.java
7780
}
7881
}
7982

@@ -133,6 +136,10 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
133136
}
134137
WeatherGlanceWidget().updateAll(appContext)
135138
}
139+
140+
AppWidgetType.CALCULATOR -> {
141+
CalculatorGlanceWidget().updateAll(appContext)
142+
}
136143
}
137144
}
138145

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package to.bitkit.appwidget
2+
3+
import android.content.Context
4+
import androidx.glance.appwidget.updateAll
5+
import dagger.hilt.android.qualifiers.ApplicationContext
6+
import kotlinx.coroutines.flow.first
7+
import to.bitkit.appwidget.model.AppWidgetType
8+
import to.bitkit.appwidget.ui.calculator.CalculatorGlanceWidget
9+
import javax.inject.Inject
10+
import javax.inject.Singleton
11+
12+
@Singleton
13+
class CalculatorAppWidgetUpdater @Inject constructor(
14+
@ApplicationContext private val context: Context,
15+
private val preferencesStore: AppWidgetPreferencesStore,
16+
) {
17+
suspend fun update() {
18+
if (!preferencesStore.hasWidgetsOfType(AppWidgetType.CALCULATOR).first()) return
19+
CalculatorGlanceWidget().updateAll(context)
20+
}
21+
}

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigActivity.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import to.bitkit.appwidget.AppWidgetRefreshWorker
1313
import to.bitkit.appwidget.model.AppWidgetType
1414
import to.bitkit.appwidget.ui.blocks.BlocksGlanceReceiver
1515
import to.bitkit.appwidget.ui.blocks.BlocksGlanceWidget
16+
import to.bitkit.appwidget.ui.calculator.CalculatorGlanceReceiver
1617
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceReceiver
1718
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceWidget
1819
import to.bitkit.appwidget.ui.price.PriceGlanceReceiver
@@ -62,6 +63,7 @@ class AppWidgetConfigActivity : ComponentActivity() {
6263
AppWidgetType.BLOCKS -> BlocksGlanceWidget().updateAll(this@AppWidgetConfigActivity)
6364
AppWidgetType.FACTS -> Unit
6465
AppWidgetType.WEATHER -> WeatherGlanceWidget().updateAll(this@AppWidgetConfigActivity)
66+
AppWidgetType.CALCULATOR -> Unit
6567
}
6668
AppWidgetRefreshWorker.enqueue(this@AppWidgetConfigActivity)
6769
val result = Intent().putExtra(
@@ -89,6 +91,7 @@ class AppWidgetConfigActivity : ComponentActivity() {
8991
HeadlinesGlanceReceiver::class.java.name -> AppWidgetType.HEADLINES
9092
BlocksGlanceReceiver::class.java.name -> AppWidgetType.BLOCKS
9193
WeatherGlanceReceiver::class.java.name -> AppWidgetType.WEATHER
94+
CalculatorGlanceReceiver::class.java.name -> AppWidgetType.CALCULATOR
9295
else -> {
9396
Logger.warn(
9497
"Encountered unknown provider class '$providerClass' " +

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ fun AppWidgetConfigScreen(
4848

4949
AppWidgetType.FACTS -> Unit
5050

51+
AppWidgetType.CALCULATOR -> Unit
52+
5153
AppWidgetType.WEATHER -> WeatherConfigContent(
5254
state = state,
5355
onSelectOption = { viewModel.selectWeatherOption(it) },

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class AppWidgetConfigViewModel @Inject constructor(
169169
AppWidgetType.BLOCKS -> it.copy(blocksPreferences = BlocksPreferences())
170170
AppWidgetType.FACTS -> it
171171
AppWidgetType.WEATHER -> it.copy(weatherPreferences = WeatherPreferences())
172+
AppWidgetType.CALCULATOR -> it
172173
}
173174
}
174175
}
@@ -184,6 +185,7 @@ class AppWidgetConfigViewModel @Inject constructor(
184185
AppWidgetType.BLOCKS -> saveBlocks(state)
185186
AppWidgetType.FACTS -> Unit
186187
AppWidgetType.WEATHER -> saveWeather(state)
188+
AppWidgetType.CALCULATOR -> Unit
187189
}
188190

189191
onComplete()

app/src/main/java/to/bitkit/appwidget/model/AppWidgetPreferences.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum class AppWidgetType {
1616
BLOCKS,
1717
FACTS,
1818
WEATHER,
19+
CALCULATOR,
1920
}
2021

2122
@Stable

0 commit comments

Comments
 (0)