Skip to content

feat: BLE電波強度(RSSI)による検出フィルター機能を追加#102

Merged
ai-kurou merged 13 commits into
mainfrom
feature/rssi-threshold-filter
May 29, 2026
Merged

feat: BLE電波強度(RSSI)による検出フィルター機能を追加#102
ai-kurou merged 13 commits into
mainfrom
feature/rssi-threshold-filter

Conversation

@ai-kurou
Copy link
Copy Markdown
Owner

@ai-kurou ai-kurou commented May 29, 2026

Summary

コア機能

  • RssiThreshold enum を追加(ALL / MEDIUM(-75 dBm) / NEAR(-65 dBm) / VERY_NEAR(-55 dBm))、デフォルト値は VERY_NEAR
  • RssiThresholdRepository インターフェースと DataStore 永続化実装を追加
  • RssiThresholdUseCase を追加(observe / update)
  • GetAppleDevicesUseCase に閾値フィルターを追加(combine で閾値未満のデバイスを除外)

設定画面 UI

  • RssiThresholdDialog を追加(ラジオボタンで閾値を選択)
  • SettingsContent の「スキャンサービス」セクション末尾に RSSI 閾値設定項目を追加
  • SettingsViewModel に RSSI 閾値の UI 状態・操作を追加し、SettingsScreen でダイアログ表示を接続

リファクタリング(コードレビュー対応)

  • GetAppleDevicesUseCaseALL 特殊分岐を削除(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

リリースノート

  • New Features
    • 設定画面に信号強度フィルター機能を追加。ユーザーは「すべて検出」「中距離まで」「近い距離のみ」「手元のみ」の4段階から検出デバイスの信号強度フィルターレベルを選択可能に。選択した閾値に基づいて、その信号強度以上のデバイスのみが検出・表示されるように改善。

Review Change Stack

- RssiThreshold enum を追加(ALL / MEDIUM(-75) / NEAR(-65) / VERY_NEAR(-55))
- RssiThresholdRepository インターフェースと DataStore 実装を追加
- RssiThresholdUseCase を追加
- GetAppleDevicesUseCase で閾値未満のデバイスをフィルタするよう更新

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d4309249-35c2-4816-9486-a1c6e68cf862

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/rssi-threshold-filter

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown
Contributor

codacy-production Bot commented May 29, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 17 complexity · 10 duplication

Metric Results
Complexity 17
Duplication 10

View in Codacy

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
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

ai-kurou and others added 7 commits May 29, 2026 12:15
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) {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 を使った場合とふるまいが一致することをコメントで明示すると意図が伝わりやすくなります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正済み

}

@Test
fun `閾値以上のRSSIを持つデバイスのみ返す`() =
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

境界値テストが不足しています。

現在のテストは「閾値より上(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)
    }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正済み


@Provides
@Singleton
fun provideRssiThresholdRepository(): RssiThresholdRepository =
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FakeRepositoryModule が返す RssiThreshold.ALL は実際のデフォルト値 RssiThreshold.VERY_NEARRssiThresholdRepositoryImpl での定義)と一致していません。

現状、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)

ai-kurou and others added 2 commits May 29, 2026 16:24
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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt (1)

31-31: 💤 Low value

FQDN ではなく 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

📥 Commits

Reviewing files that changed from the base of the PR and between e7f7a43 and c161fd0.

⛔ Files ignored due to path filters (5)
  • app/src/test/snapshots/RssiThresholdDialogKt.RssiThresholdDialogPreview.png is excluded by !**/*.png
  • app/src/test/snapshots/SettingsContentKt.SettingsContentPreviewBluetoothUnavailable.png is excluded by !**/*.png
  • app/src/test/snapshots/SettingsContentKt.SettingsContentPreviewNoWarning.png is excluded by !**/*.png
  • app/src/test/snapshots/SettingsContentKt.SettingsContentPreviewServiceRestarting.png is excluded by !**/*.png
  • app/src/test/snapshots/SettingsContentKt.SettingsContentPreviewThreeColumns.png is excluded by !**/*.png
📒 Files selected for processing (20)
  • core/data/src/main/java/kurou/androidpods/core/data/DataModule.kt
  • core/data/src/main/java/kurou/androidpods/core/data/RssiThresholdRepositoryImpl.kt
  • core/data/src/test/java/kurou/androidpods/core/data/RssiThresholdRepositoryImplTest.kt
  • core/domain/src/main/java/kurou/androidpods/core/domain/GetAppleDevicesUseCase.kt
  • core/domain/src/main/java/kurou/androidpods/core/domain/RssiThreshold.kt
  • core/domain/src/main/java/kurou/androidpods/core/domain/RssiThresholdRepository.kt
  • core/domain/src/main/java/kurou/androidpods/core/domain/RssiThresholdUseCase.kt
  • core/domain/src/test/java/kurou/androidpods/core/domain/GetAppleDevicesUseCaseTest.kt
  • feature/settings/src/main/java/kurou/androidpods/feature/settings/RssiThresholdDialog.kt
  • feature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsContent.kt
  • feature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsScreen.kt
  • feature/settings/src/main/java/kurou/androidpods/feature/settings/SettingsViewModel.kt
  • feature/settings/src/main/res/drawable/ic_rssi_threshold.xml
  • feature/settings/src/main/res/values-ja/strings.xml
  • feature/settings/src/main/res/values/strings.xml
  • feature/settings/src/test/java/kurou/androidpods/feature/settings/RssiThresholdDialogTest.kt
  • feature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsContentTest.kt
  • feature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsScreenTest.kt
  • feature/settings/src/test/java/kurou/androidpods/feature/settings/SettingsViewModelTest.kt
  • navigation/src/test/java/kurou/androidpods/navigation/FakeRepositoryModule.kt

ai-kurou and others added 3 commits May 29, 2026 16:36
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>
@sonarqubecloud
Copy link
Copy Markdown

@ai-kurou ai-kurou merged commit a39596e into main May 29, 2026
12 checks passed
@ai-kurou ai-kurou deleted the feature/rssi-threshold-filter branch May 29, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant