Skip to content

Commit 585b6a9

Browse files
committed
Delete temporary created files.
1 parent 0c84144 commit 585b6a9

File tree

10 files changed

+328
-89
lines changed

10 files changed

+328
-89
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.features.messages.impl.attachments.preview
99

1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.DisposableEffect
1112
import androidx.compose.runtime.MutableState
1213
import androidx.compose.runtime.getValue
1314
import androidx.compose.runtime.mutableStateOf
@@ -18,6 +19,7 @@ import dagger.assisted.Assisted
1819
import dagger.assisted.AssistedFactory
1920
import dagger.assisted.AssistedInject
2021
import io.element.android.features.messages.impl.attachments.Attachment
22+
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
2123
import io.element.android.libraries.architecture.Presenter
2224
import io.element.android.libraries.matrix.api.core.ProgressCallback
2325
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
@@ -36,6 +38,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
3638
@Assisted private val attachment: Attachment,
3739
private val mediaSender: MediaSender,
3840
private val permalinkBuilder: PermalinkBuilder,
41+
private val temporaryUriDeleter: TemporaryUriDeleter,
3942
) : Presenter<AttachmentsPreviewState> {
4043
@AssistedFactory
4144
interface Factory {
@@ -57,6 +60,20 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
5760

5861
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
5962

63+
DisposableEffect(Unit) {
64+
onDispose {
65+
// Delete the temporary file when the composable is disposed, in case it was not sent
66+
if (sendActionState.value == SendActionState.Idle) {
67+
// Attachment has not been sent, maybe delete it
68+
when (attachment) {
69+
is Attachment.Media -> {
70+
temporaryUriDeleter.delete(attachment.localMedia.uri)
71+
}
72+
}
73+
}
74+
}
75+
}
76+
6077
fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) {
6178
when (attachmentsPreviewEvents) {
6279
is AttachmentsPreviewEvents.SendAttachment -> {

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import io.element.android.features.messages.impl.attachments.preview.Attachments
1818
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
1919
import io.element.android.features.messages.impl.attachments.preview.SendActionState
2020
import io.element.android.features.messages.impl.fixtures.aMediaAttachment
21+
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
2122
import io.element.android.libraries.matrix.api.core.ProgressCallback
2223
import io.element.android.libraries.matrix.api.media.FileInfo
2324
import io.element.android.libraries.matrix.api.media.ImageInfo
@@ -35,6 +36,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
3536
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
3637
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
3738
import io.element.android.tests.testutils.WarmUpRule
39+
import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter
3840
import io.element.android.tests.testutils.lambda.any
3941
import io.element.android.tests.testutils.lambda.lambdaRecorder
4042
import io.element.android.tests.testutils.lambda.value
@@ -207,11 +209,13 @@ class AttachmentsPreviewPresenterTest {
207209
room: MatrixRoom = FakeMatrixRoom(),
208210
permalinkBuilder: PermalinkBuilder = FakePermalinkBuilder(),
209211
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
212+
temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(),
210213
): AttachmentsPreviewPresenter {
211214
return AttachmentsPreviewPresenter(
212215
attachment = aMediaAttachment(localMedia),
213216
mediaSender = MediaSender(mediaPreProcessor, room, InMemorySessionPreferencesStore()),
214217
permalinkBuilder = permalinkBuilder,
218+
temporaryUriDeleter = temporaryUriDeleter,
215219
)
216220
}
217221
}

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.core.net.toUri
2222
import dagger.assisted.Assisted
2323
import dagger.assisted.AssistedFactory
2424
import dagger.assisted.AssistedInject
25+
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
2526
import io.element.android.libraries.architecture.AsyncAction
2627
import io.element.android.libraries.architecture.Presenter
2728
import io.element.android.libraries.architecture.runCatchingUpdatingState
@@ -43,6 +44,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
4344
private val matrixClient: MatrixClient,
4445
private val mediaPickerProvider: PickerProvider,
4546
private val mediaPreProcessor: MediaPreProcessor,
47+
private val temporaryUriDeleter: TemporaryUriDeleter,
4648
permissionsPresenterFactory: PermissionsPresenter.Factory,
4749
) : Presenter<EditUserProfileState> {
4850
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
@@ -59,10 +61,20 @@ class EditUserProfilePresenter @AssistedInject constructor(
5961
var userAvatarUri by rememberSaveable { mutableStateOf(matrixUser.avatarUrl?.let { Uri.parse(it) }) }
6062
var userDisplayName by rememberSaveable { mutableStateOf(matrixUser.displayName) }
6163
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
62-
onResult = { uri -> if (uri != null) userAvatarUri = uri }
64+
onResult = { uri ->
65+
if (uri != null) {
66+
temporaryUriDeleter.delete(userAvatarUri)
67+
userAvatarUri = uri
68+
}
69+
}
6370
)
6471
val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker(
65-
onResult = { uri -> if (uri != null) userAvatarUri = uri }
72+
onResult = { uri ->
73+
if (uri != null) {
74+
temporaryUriDeleter.delete(userAvatarUri)
75+
userAvatarUri = uri
76+
}
77+
}
6678
)
6779

6880
val avatarActions by remember(userAvatarUri) {
@@ -96,7 +108,10 @@ class EditUserProfilePresenter @AssistedInject constructor(
96108
pendingPermissionRequest = true
97109
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
98110
}
99-
AvatarAction.Remove -> userAvatarUri = null
111+
AvatarAction.Remove -> {
112+
temporaryUriDeleter.delete(userAvatarUri)
113+
userAvatarUri = null
114+
}
100115
}
101116
}
102117

features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import app.cash.molecule.RecompositionMode
1212
import app.cash.molecule.moleculeFlow
1313
import app.cash.turbine.test
1414
import com.google.common.truth.Truth.assertThat
15+
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
1516
import io.element.android.libraries.architecture.AsyncAction
1617
import io.element.android.libraries.matrix.api.MatrixClient
1718
import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -29,6 +30,9 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenterFac
2930
import io.element.android.tests.testutils.WarmUpRule
3031
import io.element.android.tests.testutils.consumeItemsUntilPredicate
3132
import io.element.android.tests.testutils.consumeItemsUntilTimeout
33+
import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter
34+
import io.element.android.tests.testutils.lambda.lambdaRecorder
35+
import io.element.android.tests.testutils.lambda.value
3236
import io.mockk.every
3337
import io.mockk.mockk
3438
import io.mockk.mockkStatic
@@ -73,12 +77,14 @@ class EditUserProfilePresenterTest {
7377
matrixClient: MatrixClient = FakeMatrixClient(),
7478
matrixUser: MatrixUser = aMatrixUser(),
7579
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
80+
temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(),
7681
): EditUserProfilePresenter {
7782
return EditUserProfilePresenter(
7883
matrixClient = matrixClient,
7984
matrixUser = matrixUser,
8085
mediaPickerProvider = fakePickerProvider,
8186
mediaPreProcessor = fakeMediaPreProcessor,
87+
temporaryUriDeleter = temporaryUriDeleter,
8288
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
8389
)
8490
}
@@ -107,7 +113,12 @@ class EditUserProfilePresenterTest {
107113
@Test
108114
fun `present - updates state in response to changes`() = runTest {
109115
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
110-
val presenter = createEditUserProfilePresenter(matrixUser = user)
116+
val presenter = createEditUserProfilePresenter(
117+
matrixUser = user,
118+
temporaryUriDeleter = FakeTemporaryUriDeleter(
119+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
120+
),
121+
)
111122
moleculeFlow(RecompositionMode.Immediate) {
112123
presenter.present()
113124
}.test {
@@ -136,7 +147,12 @@ class EditUserProfilePresenterTest {
136147
fun `present - obtains avatar uris from gallery`() = runTest {
137148
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
138149
fakePickerProvider.givenResult(anotherAvatarUri)
139-
val presenter = createEditUserProfilePresenter(matrixUser = user)
150+
val presenter = createEditUserProfilePresenter(
151+
matrixUser = user,
152+
temporaryUriDeleter = FakeTemporaryUriDeleter(
153+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
154+
),
155+
)
140156
moleculeFlow(RecompositionMode.Immediate) {
141157
presenter.present()
142158
}.test {
@@ -154,9 +170,13 @@ class EditUserProfilePresenterTest {
154170
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
155171
fakePickerProvider.givenResult(anotherAvatarUri)
156172
val fakePermissionsPresenter = FakePermissionsPresenter()
173+
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
157174
val presenter = createEditUserProfilePresenter(
158175
matrixUser = user,
159176
permissionsPresenter = fakePermissionsPresenter,
177+
temporaryUriDeleter = FakeTemporaryUriDeleter(
178+
deleteCallback = deleteCallback,
179+
),
160180
)
161181
moleculeFlow(RecompositionMode.Immediate) {
162182
presenter.present()
@@ -177,14 +197,24 @@ class EditUserProfilePresenterTest {
177197
stateWithNewAvatar.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.TakePhoto))
178198
val stateWithNewAvatar2 = awaitItem()
179199
assertThat(stateWithNewAvatar2.userAvatarUrl).isEqualTo(userAvatarUri)
200+
deleteCallback.assertions().isCalledExactly(2).withSequence(
201+
listOf(value(userAvatarUri)),
202+
listOf(value(anotherAvatarUri)),
203+
)
180204
}
181205
}
182206

183207
@Test
184208
fun `present - updates save button state`() = runTest {
185209
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
186210
fakePickerProvider.givenResult(userAvatarUri)
187-
val presenter = createEditUserProfilePresenter(matrixUser = user)
211+
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
212+
val presenter = createEditUserProfilePresenter(
213+
matrixUser = user,
214+
temporaryUriDeleter = FakeTemporaryUriDeleter(
215+
deleteCallback = deleteCallback
216+
),
217+
)
188218
moleculeFlow(RecompositionMode.Immediate) {
189219
presenter.present()
190220
}.test {
@@ -210,14 +240,24 @@ class EditUserProfilePresenterTest {
210240
awaitItem().apply {
211241
assertThat(saveButtonEnabled).isFalse()
212242
}
243+
deleteCallback.assertions().isCalledExactly(2).withSequence(
244+
listOf(value(userAvatarUri)),
245+
listOf(value(null)),
246+
)
213247
}
214248
}
215249

216250
@Test
217251
fun `present - updates save button state when initial values are null`() = runTest {
218252
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = null)
219253
fakePickerProvider.givenResult(userAvatarUri)
220-
val presenter = createEditUserProfilePresenter(matrixUser = user)
254+
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
255+
val presenter = createEditUserProfilePresenter(
256+
matrixUser = user,
257+
temporaryUriDeleter = FakeTemporaryUriDeleter(
258+
deleteCallback = deleteCallback
259+
),
260+
)
221261
moleculeFlow(RecompositionMode.Immediate) {
222262
presenter.present()
223263
}.test {
@@ -243,6 +283,10 @@ class EditUserProfilePresenterTest {
243283
awaitItem().apply {
244284
assertThat(saveButtonEnabled).isFalse()
245285
}
286+
deleteCallback.assertions().isCalledExactly(2).withSequence(
287+
listOf(value(null)),
288+
listOf(value(userAvatarUri)),
289+
)
246290
}
247291
}
248292

@@ -252,7 +296,10 @@ class EditUserProfilePresenterTest {
252296
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
253297
val presenter = createEditUserProfilePresenter(
254298
matrixClient = matrixClient,
255-
matrixUser = user
299+
matrixUser = user,
300+
temporaryUriDeleter = FakeTemporaryUriDeleter(
301+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
302+
),
256303
)
257304
moleculeFlow(RecompositionMode.Immediate) {
258305
presenter.present()
@@ -318,7 +365,10 @@ class EditUserProfilePresenterTest {
318365
givenPickerReturnsFile()
319366
val presenter = createEditUserProfilePresenter(
320367
matrixClient = matrixClient,
321-
matrixUser = user
368+
matrixUser = user,
369+
temporaryUriDeleter = FakeTemporaryUriDeleter(
370+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
371+
),
322372
)
323373
moleculeFlow(RecompositionMode.Immediate) {
324374
presenter.present()
@@ -337,7 +387,10 @@ class EditUserProfilePresenterTest {
337387
val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL)
338388
val presenter = createEditUserProfilePresenter(
339389
matrixClient = matrixClient,
340-
matrixUser = user
390+
matrixUser = user,
391+
temporaryUriDeleter = FakeTemporaryUriDeleter(
392+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
393+
),
341394
)
342395
fakePickerProvider.givenResult(anotherAvatarUri)
343396
fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no")))
@@ -403,7 +456,13 @@ class EditUserProfilePresenterTest {
403456
}
404457

405458
private suspend fun saveAndAssertFailure(matrixUser: MatrixUser, matrixClient: MatrixClient, event: EditUserProfileEvents) {
406-
val presenter = createEditUserProfilePresenter(matrixUser = matrixUser, matrixClient = matrixClient)
459+
val presenter = createEditUserProfilePresenter(
460+
matrixUser = matrixUser,
461+
matrixClient = matrixClient,
462+
temporaryUriDeleter = FakeTemporaryUriDeleter(
463+
deleteCallback = { assertThat(it).isEqualTo(userAvatarUri) }
464+
),
465+
)
407466
moleculeFlow(RecompositionMode.Immediate) {
408467
presenter.present()
409468
}.test {

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope
2020
import androidx.compose.runtime.saveable.rememberSaveable
2121
import androidx.compose.runtime.setValue
2222
import androidx.core.net.toUri
23+
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
2324
import io.element.android.libraries.architecture.AsyncAction
2425
import io.element.android.libraries.architecture.Presenter
2526
import io.element.android.libraries.architecture.runCatchingUpdatingState
@@ -45,6 +46,7 @@ class RoomDetailsEditPresenter @Inject constructor(
4546
private val room: MatrixRoom,
4647
private val mediaPickerProvider: PickerProvider,
4748
private val mediaPreProcessor: MediaPreProcessor,
49+
private val temporaryUriDeleter: TemporaryUriDeleter,
4850
permissionsPresenterFactory: PermissionsPresenter.Factory,
4951
) : Presenter<RoomDetailsEditState> {
5052
private val cameraPermissionPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
@@ -59,6 +61,7 @@ class RoomDetailsEditPresenter @Inject constructor(
5961
var roomAvatarUriEdited by rememberSaveable { mutableStateOf<Uri?>(null) }
6062
LaunchedEffect(roomAvatarUri) {
6163
// Every time the roomAvatar change (from sync), we can set the new avatar.
64+
temporaryUriDeleter.delete(roomAvatarUriEdited)
6265
roomAvatarUriEdited = roomAvatarUri
6366
}
6467

@@ -98,10 +101,20 @@ class RoomDetailsEditPresenter @Inject constructor(
98101
}
99102

100103
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
101-
onResult = { uri -> if (uri != null) roomAvatarUriEdited = uri }
104+
onResult = { uri ->
105+
if (uri != null) {
106+
temporaryUriDeleter.delete(roomAvatarUriEdited)
107+
roomAvatarUriEdited = uri
108+
}
109+
}
102110
)
103111
val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker(
104-
onResult = { uri -> if (uri != null) roomAvatarUriEdited = uri }
112+
onResult = { uri ->
113+
if (uri != null) {
114+
temporaryUriDeleter.delete(roomAvatarUriEdited)
115+
roomAvatarUriEdited = uri
116+
}
117+
}
105118
)
106119

107120
LaunchedEffect(cameraPermissionState.permissionGranted) {
@@ -143,7 +156,10 @@ class RoomDetailsEditPresenter @Inject constructor(
143156
pendingPermissionRequest = true
144157
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
145158
}
146-
AvatarAction.Remove -> roomAvatarUriEdited = null
159+
AvatarAction.Remove -> {
160+
temporaryUriDeleter.delete(roomAvatarUriEdited)
161+
roomAvatarUriEdited = null
162+
}
147163
}
148164
}
149165

0 commit comments

Comments
 (0)