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
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,11 @@ internal class CreateGroupViewModel @Inject constructor(
).onSuccess { chat ->
// The chat exists, so the key has done its job — a later Create is a second group.
attempt.clear()
// The cached local copy is redundant now that the blob is the chat's picture.
discardPendingImage()
// The cached local copy is redundant now that the blob is the chat's picture, but the
// form is still on screen for the length of the hold below and its tile is still
// drawing from that file. Discarding it here crossfaded the picture back to the empty
// camera tile a beat before the flow left for the chat, so the pick read as dropped at
// the moment it was accepted. `onCleared` deletes it once the flow entry has popped.
dispatchSuccessThen(Event.UpdateProcessingState(success = true)) {
dispatchEvent(Event.ChatCreated(chat))
dispatchEvent(Event.UpdateProcessingState())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.getcode.solana.keys.Mint
import com.getcode.solana.keys.PublicKey
import com.getcode.util.resources.ContentReader
import com.getcode.util.resources.FakeResourceHelper
import com.getcode.view.SuccessHoldDuration
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
Expand All @@ -36,6 +37,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.After
Expand Down Expand Up @@ -308,4 +310,43 @@ class CreateGroupViewModelErrorTest {
assertEquals(2, keys.size)
assertTrue(!keys[0].bytes.contentEquals(keys[1].bytes))
}

/**
* The checkmark is held for half a second before the flow leaves for the chat, and the form is
* on screen for all of it. The create's own cache cleanup used to run at the top of that hold,
* which crossfaded the picture back to the empty camera tile a beat before the screen went
* away — the pick reading as dropped at the moment it was accepted.
*
* So the local copy outlives the screen that shows it, exactly as the photo-selection step
* leaves its own pick to `onCleared`: the flow entry pops, the shared view model clears, and
* the file goes with it.
*/
@Test
fun `a created group keeps its picture until the form is gone`() =
runTest(mainCoroutineRule.dispatcher) {
dispatchers = TestDispatchers(testScheduler)
val vm = createViewModel()
vm.completeDraft()

val uri = mockk<Uri>(relaxed = true)
every { contentReader.readBytes(uri) } returns ByteArray(8)
coEvery { blobStorage.upload(any(), any()) } returns
Result.success(BlobId(ByteArray(32) { 5 }))
vm.dispatchEvent(CreateGroupViewModel.Event.OnImageCached(uri, "image/jpeg"))
coEvery { chatCoordinator.create(any(), any()) } returns Result.success(created())

vm.dispatchEvent(CreateGroupViewModel.Event.CreateRequested)
advanceTimeBy(SuccessHoldDuration / 2)

// Mid-hold: the button is showing success and the picture is still under it.
assertTrue(vm.stateFlow.value.processingState.success)
assertEquals(uri, vm.stateFlow.value.image.dataOrNull)
// And the file it points at is still there — it is what the tile is drawing from.
coVerify(exactly = 0) { contentReader.removeFromCache(uri) }

advanceUntilIdle()

assertEquals(created(), vm.stateFlow.value.created)
assertEquals(uri, vm.stateFlow.value.image.dataOrNull)
}
}
Loading