feat: BLE電波強度(RSSI)による検出フィルター機能を追加#102
Conversation
- RssiThreshold enum を追加(ALL / MEDIUM(-75) / NEAR(-65) / VERY_NEAR(-55)) - RssiThresholdRepository インターフェースと DataStore 実装を追加 - RssiThresholdUseCase を追加 - GetAppleDevicesUseCase で閾値未満のデバイスをフィルタするよう更新 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 17 |
| Duplication | 10 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RssiThresholdUseCaseを注入し、uiStateにrssiThresholdを反映 - showRssiThresholdDialog / dismissRssiThresholdDialog / updateRssiThresholdを追加 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RssiThresholdDialogを追加(ラジオボタン選択式) - RssiThreshold.toStringRes()拡張関数をSettingsContent.ktに追加 - 英語・日本語の文字列リソースを追加 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
スキャンサービスセクションの末尾にRSSI閾値フィルターの設定項目を追加し、 タップするとRssiThresholdDialogが表示されるようにした。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ogと設計を統一 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| fun observe(): Flow<Map<String, AppleDevice>> = repository.observeDevices() | ||
| fun observe(): Flow<Map<String, AppleDevice>> = | ||
| combine(repository.observeDevices(), rssiThresholdRepository.observe()) { devices, threshold -> | ||
| if (threshold == RssiThreshold.ALL) { |
There was a problem hiding this comment.
if (threshold == RssiThreshold.ALL) の分岐は冗長です。
ALL.minRssi = Int.MIN_VALUE = -2147483648 であり、BLE の RSSI 値は実用上 -30〜-100 dBm の範囲(すべて Int.MIN_VALUE より大きい)に収まるため、else 側の devices.filter { device.rssi >= threshold.minRssi } だけで ALL の場合も全デバイスを返せます。
// After(特殊ケース不要)
devices.filter { (_, device) -> device.rssi >= threshold.minRssi }特殊ケースを残す場合は、ALL.minRssi を使った場合とふるまいが一致することをコメントで明示すると意図が伝わりやすくなります。
| } | ||
|
|
||
| @Test | ||
| fun `閾値以上のRSSIを持つデバイスのみ返す`() = |
There was a problem hiding this comment.
境界値テストが不足しています。
現在のテストは「閾値より上(rssi=-60)」と「閾値より下(rssi=-80)」のみ検証しており、「閾値にぴったり等しい値(rssi=-75)」のケースがありません。フィルター条件が >= から誤って > に変更された場合、境界値テストがなければ気づけません。
CLAUDE.md の方針(「境界値の軸を意識する」)に沿って1ケース追加することを推奨します。
@Test
fun `閾値と等しいRSSIを持つデバイスは含まれる`() =
runTest {
val boundaryDevice = device(rssi = -75) // MEDIUM.minRssi と同値
every { repository.observeDevices() } returns MutableStateFlow(mapOf("boundary" to boundaryDevice))
every { rssiThresholdRepository.observe() } returns MutableStateFlow(RssiThreshold.MEDIUM)
val result = useCase.observe().first()
assertEquals(mapOf("boundary" to boundaryDevice), result)
}|
|
||
| @Provides | ||
| @Singleton | ||
| fun provideRssiThresholdRepository(): RssiThresholdRepository = |
There was a problem hiding this comment.
FakeRepositoryModule が返す RssiThreshold.ALL は実際のデフォルト値 RssiThreshold.VERY_NEAR(RssiThresholdRepositoryImpl での定義)と一致していません。
現状、AppScaffoldTest は設定値の表示ラベルをアサートしていないため動作上の問題は生じていませんが、ナビゲーションテスト上の設定画面では「Signal Strength Filter」が「All devices」と表示されており、実機の初回起動時の表示(「Very near (~1m)」)と異なります。
将来テストがラベル文字列をアサートする際にずれが生じないよう、実際のデフォルト値に合わせることを推奨します。
// Before
override fun observe(): Flow<RssiThreshold> = flowOf(RssiThreshold.ALL)
// After
override fun observe(): Flow<RssiThreshold> = flowOf(RssiThreshold.VERY_NEAR)ALL.minRssi = Int.MIN_VALUE のため、filter式がALLの場合も全デバイスを返すことが保証される。 また境界値(閾値ちょうどのRSSI)のテストを追加する。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
実際のRssiThresholdRepositoryImplのデフォルト値と一致させる。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt (1)
31-31: 💤 Low valueFQDN ではなく import の利用を推奨します。
android.app.Applicationを完全修飾名でインライン参照しています。Lint の FQDN 警告を避けるため、import して利用してください。♻️ 修正案
+import android.app.Application- val context = ApplicationProvider.getApplicationContext<android.app.Application>() + val context = ApplicationProvider.getApplicationContext<Application>()As per coding guidelines: "Resolve all warnings (FQDN usage, deprecated APIs, unused imports) before reporting."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt` at line 31, Replace the fully-qualified android.app.Application usage in the test with an import and the simple type name: add an import for android.app.Application and change the call to ApplicationProvider.getApplicationContext<Application>() in RssiThresholdRepositoryImplTest (the ApplicationProvider.getApplicationContext reference) so the code uses the imported Application type instead of the FQDN to satisfy lint rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt`:
- Around line 56-57: Rename the test method whose current name is
`IOExceptionが発生した場合はデフォルト値ALLを返す` in RssiThresholdRepositoryImplTest to match
the actual assertion expecting `RssiThreshold.VERY_NEAR` (e.g.,
`IOExceptionが発生した場合はデフォルト値VERY_NEARを返す`), so the method name reflects the
implemented behavior; update any related test display names or comments to
reference `RssiThreshold.VERY_NEAR` as the default.
In
`@feature/settings/src/main/java/kurou/androidpods/feature/settings/RssiThresholdDialog.kt`:
- Around line 32-45: Replace the dual click handlers in RssiThresholdDialog by
making the entire selectable row use Modifier.selectable(selected = threshold ==
currentThreshold, onClick = { onThresholdSelected(threshold) }, role =
Role.RadioButton) and set RadioButton(onClick = null) so there is a single
accessibility focusable node; update the same pattern in ThemeModeDialog and
OverlayPositionDialog to use Modifier.selectable with role = Role.RadioButton,
keep using the existing onThresholdSelected/currentThreshold (or equivalent
callbacks/state in those files) and the same Text(stringResource(...)) call.
---
Nitpick comments:
In
`@core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt`:
- Line 31: Replace the fully-qualified android.app.Application usage in the test
with an import and the simple type name: add an import for
android.app.Application and change the call to
ApplicationProvider.getApplicationContext<Application>() in
RssiThresholdRepositoryImplTest (the ApplicationProvider.getApplicationContext
reference) so the code uses the imported Application type instead of the FQDN to
satisfy lint rules.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0541c414-ffbd-43af-9d41-34b9a65fc1da
⛔ Files ignored due to path filters (5)
app/src/test/snapshots/RssiThresholdDialogKt.RssiThresholdDialogPreview.pngis excluded by!**/*.pngapp/src/test/snapshots/SettingsContentKt.SettingsContentPreviewBluetoothUnavailable.pngis excluded by!**/*.pngapp/src/test/snapshots/SettingsContentKt.SettingsContentPreviewNoWarning.pngis excluded by!**/*.pngapp/src/test/snapshots/SettingsContentKt.SettingsContentPreviewServiceRestarting.pngis excluded by!**/*.pngapp/src/test/snapshots/SettingsContentKt.SettingsContentPreviewThreeColumns.pngis excluded by!**/*.png
📒 Files selected for processing (20)
core/data/src/main/java/kurou/androidpods/core/data/DataModule.ktcore/data/src/main/java/kurou/androidpods/core/data/RssiThresholdRepositoryImpl.ktcore/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.ktcore/domain/src/main/java/kurou/androidpods/core/domain/GetAppleDevicesUseCase.ktcore/domain/src/main/java/kurou/androidpods/core/domain/RssiThreshold.ktcore/domain/src/main/java/kurou/androidpods/core/domain/RssiThresholdRepository.ktcore/domain/src/main/java/kurou/androidpods/core/domain/RssiThresholdUseCase.ktcore/domain/src/test/java/kurou/androidpods/core/domain/GetAppleDevicesUseCaseTest.ktfeature/settings/src/main/java/kurou/androidpods/feature/settings/RssiThresholdDialog.ktfeature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsContent.ktfeature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsScreen.ktfeature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsViewModel.ktfeature/settings/src/main/res/drawable/ic_rssi_threshold.xmlfeature/settings/src/main/res/values-ja/strings.xmlfeature/settings/src/main/res/values/strings.xmlfeature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.ktfeature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsContentTest.ktfeature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsScreenTest.ktfeature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsViewModelTest.ktnavigation/src/test/java/kurou/androidpods/navigation/FakeRepositoryModule.kt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Row にModifier.selectable(role = Role.RadioButton)を適用し、 RadioButton の onClick を null にすることで単一のアクセシビリティ フォーカスノードに統一する。minimumInteractiveComponentSizeを明示的に 付与して視覚的なサイズを維持する。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|



Summary
コア機能
RssiThresholdenum を追加(ALL/MEDIUM(-75 dBm) /NEAR(-65 dBm) /VERY_NEAR(-55 dBm))、デフォルト値はVERY_NEARRssiThresholdRepositoryインターフェースと DataStore 永続化実装を追加RssiThresholdUseCaseを追加(observe / update)GetAppleDevicesUseCaseに閾値フィルターを追加(combineで閾値未満のデバイスを除外)設定画面 UI
RssiThresholdDialogを追加(ラジオボタンで閾値を選択)SettingsContentの「スキャンサービス」セクション末尾に RSSI 閾値設定項目を追加SettingsViewModelに RSSI 閾値の UI 状態・操作を追加し、SettingsScreenでダイアログ表示を接続リファクタリング(コードレビュー対応)
GetAppleDevicesUseCaseのALL特殊分岐を削除(ALL.minRssi = Int.MIN_VALUEにより filter 式だけで全デバイス通過が保証されるため不要)RssiThresholdDialogの dismiss を ViewModel 内部から Screen 側に移動し、OverlayPositionDialogと設計を統一FakeRepositoryModuleの RSSI 閾値デフォルト値を実装と一致するVERY_NEARに修正Test plan
RssiThresholdRepositoryImplTest: デフォルト値がVERY_NEAR、保存・取得、IOException ハンドリング、非 IOException は伝播GetAppleDevicesUseCaseTest:ALLでは全デバイスを返す、閾値以上のデバイスのみ返す、境界値(閾値ちょうど)のデバイスは含まれるRssiThresholdDialogTest: 全選択肢が表示される、選択肢タップでonThresholdSelectedが呼ばれるSettingsContentTest: RSSI 閾値アイテムタップでonRssiThresholdClickが呼ばれるSettingsViewModelTest:updateRssiThresholdで UseCase の update が呼ばれる、ダイアログ表示・非表示の制御SettingsScreenTest: RSSI 閾値アイテムタップでRssiThresholdDialogが表示され、選択後に閉じる🤖 Generated with Claude Code
Summary by CodeRabbit
リリースノート