Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
| **연습 시작과 마무리** | 시작과 종료 시각, 당시 코스와 과제를 저장합니다. 주차 중 홈에서 진행 중 과제와 다음 연습 과제를 구분해 보고, 최근에 마친 연습의 복습을 다시 열 수 있습니다. |
| **활성 연습의 차량 상태 관찰** | SDK가 전달한 기어, 방향지시등과 브레이크 상태를 활성 연습에 기록합니다. 기본은 앱 화면이 켜진 동안만 수집하며, 주차와 UX 허용을 확인하고 사용자가 내비 중 기록을 켠 경우에만 화면 밖에서도 이어갑니다. 미확인, 미지원, 거부와 오류를 보존합니다. |
| **명시적으로 켠 전면 위치 표본 기록** | 진행 중인 연습에서 주차, UX 허용과 위치 권한을 확인한 뒤 직접 켭니다. 한길 화면이 켜진 동안 SDK 업데이트 표본을 기기에 저장하고, 화면 밖으로 나가거나 연습 변경/종료, 권한 상실, 오류 또는 저장 상한에서 멈춥니다. 같은 앱 실행에서 주차 후 복귀하면 같은 진행 중 연습의 화면 이탈 중단을 안내하며, 주차 중 위치 기록 화면에서 새 기록을 직접 켤 수 있습니다. |
| **주차 후 위치 표본 확인** | 선택한 종료 연습의 위치 기록 실행과 표본, 표본 없는 콜백과 수집 상태를 저장 순서대로 확인하고, 직접 펼친 지도에서 같은 실행에 저장한 위치 표본들과 선택한 복습 도로를 함께 보고 선택 표본을 구분합니다. 수신이 이어진 표본 사이는 점선으로 구분하고, 좌표 없는 콜백과 수신 공백은 이어 표시하지 않습니다. 복습 도로를 아직 고르지 않았다면 같은 코스의 도로선까지 거리를 비교하고, 배경 타일 없는 미리보기에서 선택 표본과 도로선 위 최근접 지점을 확인합니다. 선택 표본 주변의 연속 기록이 한 도로선에 가까우면 후보로 안내하고, 사용자가 고른 도로와 선택 표본을 함께 저장합니다. 이미 고른 도로까지의 거리도 확인할 수 있습니다. 연결한 표본 주변의 연속 기록이 한 도로선을 일관되게 가리키면 그 부분을 강조합니다. 해당 구간을 직접 고르면 공개 도로 범위를 다음 연습 목표로 가져올 수 있습니다. 지도 배경을 불러오지 못해도 저장 표본과 고른 도로를 확인할 수 있습니다. 연결한 표본을 교체하거나 해제하고 도로 메모와 연습 전체 메모를 읽습니다. 비교 지도에서 연결한 표본의 상세를 바로 열 수 있습니다. 직접 연결은 실제 이동이나 자동 위치 매칭을 뜻하지 않습니다. SDK 시각과 신뢰도 원본값의 미확인 의미를 유지하며 현재 위치, 정확도나 실제 이동 경로로 표시하지 않습니다. 같은 앱 실행 안에서 화면을 다시 만들면 선택한 연습을 유지합니다. |
| **주차 후 위치 표본 확인** | 선택한 종료 연습의 위치 기록 실행과 표본, 표본 없는 콜백과 수집 상태를 저장 순서대로 확인하고, 직접 펼친 지도에서 같은 실행에 저장한 위치 표본들과 선택한 복습 도로를 함께 보고 선택 표본을 구분합니다. 수신이 이어진 표본 사이는 점선으로 구분하고, 좌표 없는 콜백과 수신 공백은 이어 표시하지 않습니다. 복습 도로를 아직 고르지 않았다면 같은 코스의 도로선까지 거리를 비교하고, 배경 타일 없는 미리보기에서 선택 표본과 도로선 위 최근접 지점을 확인합니다. 선택 표본 주변의 연속 기록이 한 도로선에 가까우면 후보로 안내하고, 사용자가 고른 도로와 선택 표본을 함께 저장합니다. 이미 고른 도로까지의 거리도 확인할 수 있습니다. 연결한 표본 주변의 연속 기록이 한 도로선을 일관되게 가리키면 그 부분을 강조합니다. 해당 구간을 직접 고르면 공개 도로 범위를 다음 연습 목표로 가져올 수 있습니다. 저장 표본과 고른 도로는 외부 배경 없이 먼저 표시하고, 사용자가 선택할 때만 주변 배경 타일을 요청합니다. 연결한 표본을 교체하거나 해제하고 도로 메모와 연습 전체 메모를 읽습니다. 비교 지도에서 연결한 표본의 상세를 바로 열 수 있습니다. 직접 연결은 실제 이동이나 자동 위치 매칭을 뜻하지 않습니다. SDK 시각과 신뢰도 원본값의 미확인 의미를 유지하며 현재 위치, 정확도나 실제 이동 경로로 표시하지 않습니다. 같은 앱 실행 안에서 화면을 다시 만들면 선택한 연습을 유지합니다. |
| **주차 후 차량 기록 확인** | 최근 종료 세션과 지난 연습에서 선택한 종료 기록의 저장된 SDK 관찰을 한 번에 하나씩 확인합니다. 당시 기어, 방향지시등과 브레이크, 연결과 앱 관찰 시각을 보고 이전/다음으로 저장 순서를 따라갑니다. |
| **지난 연습 열람** | 종료 이력을 저장 순서대로 한 번에 하나씩 확인합니다. 당시 목표, 코스, 과제와 시작/마무리 버튼 시각을 보여 주며, 이탈과 주행 잠금 후에도 같은 기록으로 돌아옵니다. |
| **저장된 배경 기록 실행 확인** | 선택한 종료 이력의 배경 기록 실행을 펼쳐 저장 순서와 시작/종료 기록을 읽습니다. 종료를 확인하지 못한 이전 실행을 계속 표시하며, 저장된 상태를 현재 수집 중이라는 의미나 실제 SDK 수집 구간으로 해석하지 않습니다. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,8 @@ class LocationRecordingFlowTest {
.Builder()
.addInterceptor { chain ->
val request = chain.request()
check(request.url.host == "tiles.openfreemap.org" && request.url.encodedPath.startsWith("/styles/"))
requests.incrementAndGet()
check(request.url.host == "tiles.openfreemap.org" && request.url.encodedPath.startsWith("/styles/"))
val available = basemapAvailable.get()
val json = """{"version":8,"sources":{},"layers":[{"id":"test-remote-background","type":"background"}]}"""
Response
Expand Down Expand Up @@ -954,48 +954,80 @@ class LocationRecordingFlowTest {
.first()
}

fun awaitOffline(road: CourseRoadSnapshot? = null): MapView {
waitTag("stored_location_map_without_background")
assertText("stored_location_map_without_background", MapR.string.stored_map_without_background)
assertTrue(nodes("stored_location_map_failed").isEmpty())
return awaitStoredMap(run, recorded.events, selected, road, trace, expectedOffline = true)
}
fun awaitLocal(road: CourseRoadSnapshot? = null): MapView =
awaitStoredMap(run, recorded.events, selected, road, trace, expectedOffline = true)

click("home_history")
openSample()
val original = awaitOffline()
assertTrue("The requested basemap must return an error before fallback", requests.get() > 0)
val original = awaitLocal()
waitTag("stored_location_map_show_background")
assertText("stored_location_map_background_notice", MapR.string.stored_map_background_notice)
assertEquals("Opening the stored map must not request an external style", 0, requests.get())
assertNull(storedRoad())
click("stored_location_course_road_save_0")
waitTag("review_road_sample_current")
val linked = checkNotNull(storedRoad())
assertEquals(expectedRoad, linked.road)
assertEquals(expectedSample, linked.sampleAnchor)
val afterSave = awaitOffline(expectedRoad)
val afterSave = awaitLocal(expectedRoad)
instrument.runOnMainSync { assertFalse(original.isAttachedToWindow) }
click("location_history_back")
assertNoStoredMap(afterSave)
openSample()
val reopened = awaitOffline(expectedRoad)
val staleRetry = clickAction("stored_location_map_retry_background")
val reopened = awaitLocal(expectedRoad)
assertEquals("Saving or reopening the stored map must not opt in", 0, requests.get())
click("stored_location_map_show_background")
waitTag("stored_location_map_without_background")
assertText("stored_location_map_without_background", MapR.string.stored_map_without_background)
assertTrue(nodes("stored_location_map_failed").isEmpty())
val fallback = awaitLocal(expectedRoad)
val beforeRetry = requests.get()
assertTrue("Only the explicit action should request the failing style", beforeRetry > 0)
instrument.runOnMainSync { assertFalse(reopened.isAttachedToWindow) }

val staleRetry = clickAction("stored_location_map_retry_background")
basemapAvailable.set(true)
click("stored_location_map_retry_background")
compose.waitUntil(20_000) {
requests.get() > beforeRetry && nodes("stored_location_map_without_background").isEmpty() &&
nodes("stored_location_map_ready").size == 1
}
val restored = awaitStoredMap(run, recorded.events, selected, expectedRoad, trace)
instrument.runOnMainSync { assertFalse(reopened.isAttachedToWindow) }
compose
.onNodeWithText(compose.activity.getString(MapR.string.map_sources), useUnmergedTree = true)
.performScrollTo()
.assertIsDisplayed()
val configuration = compose.activity.resources.configuration
val dark = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
if (dark) {
compose
.onNodeWithTag("map_dark_design_credit", useUnmergedTree = true)
.performScrollTo()
.assertIsDisplayed()
.assertTextContains("CartoDB Basemaps", substring = true)
} else {
assertTrue(nodes("map_dark_design_credit").isEmpty())
}
instrument.runOnMainSync { assertFalse(fallback.isAttachedToWindow) }
val staleHide = clickAction("stored_location_map_hide_background")
click("stored_location_map_hide_background")
val hidden = awaitLocal(expectedRoad)
instrument.runOnMainSync { assertFalse(restored.isAttachedToWindow) }
val afterHide = requests.get()
invoke(staleHide)
compose.waitForIdle()
assertTrue(nodes("stored_location_map_show_background").size == 1)
assertEquals("A retained hide action must not enable the external style", afterHide, requests.get())
val staleShow = clickAction("stored_location_map_show_background")
f.drive(true)
waitTag("location_history_locked")
assertNoStoredMap(restored)
assertNoStoredMap(hidden)
invoke(staleRetry)
invoke(staleShow)
assertNoStoredMap()
assertEquals("Retired controls must not request another style", afterHide, requests.get())
assertEquals(linked, storedRoad())
assertEquals(recorded, f.snapshot(ended))
val configuration = compose.activity.resources.configuration
val dark = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
args.getString("expectedDark")?.let { assertEquals(it.toBooleanStrict(), dark) }
args.getString("expectedFontScale")?.let { assertEquals(it.toFloat(), configuration.fontScale, 0.01f) }
f.assertNoPlatformActions()
Expand Down Expand Up @@ -1138,25 +1170,16 @@ class LocationRecordingFlowTest {
assertTrue(nodes("review_road_save").isEmpty() && nodes("review_road_clear").isEmpty())
assertTrue(nodes("review_road_memo_edit").isEmpty() && nodes("review_road_memo_create").isEmpty())
visual("stored-point-and-user-selected-road", "location_history_point_map_notice", "stored_location_map")
for (source in listOf(MapR.string.map_sources, MapR.string.map_road_attribution)) {
compose
.onNodeWithText(compose.activity.getString(source), useUnmergedTree = true)
.performScrollTo()
.assertIsDisplayed()
}
compose
.onNodeWithText(compose.activity.getString(MapR.string.map_road_attribution), useUnmergedTree = true)
.performScrollTo()
.assertIsDisplayed()
assertFalse(hasTextNode(compose.activity.getString(MapR.string.map_sources)))
assertTrue(nodes("map_dark_design_credit").isEmpty())
val configuration = compose.activity.resources.configuration
val dark = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
args.getString("expectedDark")?.let { assertEquals(it.toBooleanStrict(), dark) }
args.getString("expectedFontScale")?.let { assertEquals(it.toFloat(), configuration.fontScale, 0.01f) }
if (dark) {
compose
.onNodeWithTag("map_dark_design_credit", useUnmergedTree = true)
.performScrollTo()
.assertIsDisplayed()
.assertTextContains("CartoDB Basemaps", substring = true)
} else {
assertTrue(nodes("map_dark_design_credit").isEmpty())
}

waitTag("review_road_sample_absent")
val staleFirstLink = clickAction("review_road_sample_link")
Expand Down
2 changes: 1 addition & 1 deletion feature/home/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
<string name="history_memo_reloaded">저장된 메모를 다시 읽었어요.</string>
<string name="history_memo_entered_text">입력한 내용이에요. 저장 여부는 다시 읽어 확인해 주세요.</string>
<string name="location_history_point_map">이 기록의 위치 표본 지도</string>
<string name="location_history_point_map_notice">기본 지도를 펼치면 이 실행의 저장 표본과 저장한 도로 주변의 타일을 외부 지도 서비스에 요청해요. 후보 도로 미리보기는 배경 타일 없이 그려요.</string>
<string name="location_history_point_map_notice">저장 표본과 고른 도로를 먼저 보여 줘요. 외부 지도 배경은 필요할 때만 켤 수 있어요.</string>
<string name="location_history_memo">이 연습 전체 메모</string>
<string name="review_road_title">내가 고른 복습 구간</string>
<string name="review_road_scope">내가 고른 공개지도 구간이에요. 실제 이동과 현재 통행은 확인하지 않았어요.</string>
Expand Down
Loading
Loading