Skip to content

Commit 6ced08d

Browse files
committed
Moved most logic to viewmodel
1 parent 76fd508 commit 6ced08d

File tree

5 files changed

+124
-133
lines changed

5 files changed

+124
-133
lines changed

app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import javax.inject.Inject
5656
*/
5757
@AndroidEntryPoint
5858
class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller,
59-
MediaPickerItemFragment.Controller, MediaSendFragment.Controller,
59+
MediaPickerItemComposeFragment.Controller, MediaSendFragment.Controller,
6060
ImageEditorFragment.Controller {
6161

6262
private var recipient: Recipient? = null
@@ -183,8 +183,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme
183183

184184
val fragment = MediaPickerItemComposeFragment.newInstance(
185185
folder.bucketId,
186-
folder.title,
187-
MediaSendViewModel.MAX_SELECTED_FILES
186+
folder.title
188187
)
189188
supportFragmentManager.beginTransaction()
190189
.setCustomAnimations(
@@ -214,7 +213,7 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme
214213
recipient!!
215214
)
216215
val itemFragment =
217-
MediaPickerItemComposeFragment.newInstance(bucketId, "", MediaSendViewModel.MAX_SELECTED_FILES)
216+
MediaPickerItemComposeFragment.newInstance(bucketId, "")
218217

219218
supportFragmentManager.beginTransaction()
220219
.setCustomAnimations(

app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.kt

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,26 @@ class MediaSendViewModel @Inject constructor(
9191
)
9292
}
9393

94+
fun onMediaSelected(media: Media) {
95+
val updatedList = run {
96+
val current = uiState.value.selectedMedia
97+
val exists = current.any { it.uri == media.uri }
98+
99+
if (exists) {
100+
current.filterNot { it.uri == media.uri }
101+
} else {
102+
if (current.size >= MAX_SELECTED_FILES) {
103+
_effects.tryEmit(MediaSendEffect.ShowError(Error.TOO_MANY_ITEMS))
104+
current
105+
} else {
106+
current + media
107+
}
108+
}
109+
}
110+
111+
onSelectedMediaChanged(updatedList)
112+
}
113+
94114
fun onSelectedMediaChanged(newMedia: List<Media?>) {
95115
repository.getPopulatedMedia(context, newMedia) { populatedMedia: List<Media> ->
96116
runOnMain {
@@ -141,7 +161,6 @@ class MediaSendViewModel @Inject constructor(
141161
selectedMedia = filteredMedia,
142162
bucketId = computedId,
143163
countVisibility = newVisibility,
144-
forcedMultiSelect = it.forcedMultiSelect && filteredMedia.isNotEmpty()
145164
)
146165
}
147166
}
@@ -176,18 +195,18 @@ class MediaSendViewModel @Inject constructor(
176195
selectedMedia = filteredMedia,
177196
bucketId = newBucketId,
178197
countVisibility = CountButtonState.Visibility.FORCED_OFF,
179-
forcedMultiSelect = false
180198
)
181199
}
182200
}
183201
}
184202
}
185203

186204
fun onMultiSelectStarted() {
187-
_uiState.update { it.copy(
188-
countVisibility = CountButtonState.Visibility.FORCED_ON,
189-
forcedMultiSelect = true
190-
) }
205+
_uiState.update {
206+
it.copy(
207+
countVisibility = CountButtonState.Visibility.FORCED_ON
208+
)
209+
}
191210
}
192211

193212
fun onImageEditorStarted() {
@@ -386,6 +405,11 @@ class MediaSendViewModel @Inject constructor(
386405
media: List<Media>,
387406
mediaConstraints: MediaConstraints
388407
): Pair<List<Media>, Set<Error>> {
408+
409+
if (media.isEmpty()) {
410+
return Pair(emptyList(), emptySet())
411+
}
412+
389413
val validMedia = ArrayList<Media>()
390414
val errors = HashSet<Error>()
391415

@@ -475,9 +499,14 @@ class MediaSendViewModel @Inject constructor(
475499
val position: Int = -1,
476500
val countVisibility: CountButtonState.Visibility = CountButtonState.Visibility.FORCED_OFF,
477501
val showCameraButton: Boolean = false,
478-
val forcedMultiSelect: Boolean = false, // previously in the adapter but put this here for now
479502
) {
480503
val count: Int get() = selectedMedia.size
504+
505+
val isMultiSelect: Boolean
506+
get() = selectedMedia.isNotEmpty() || countVisibility == CountButtonState.Visibility.FORCED_ON
507+
508+
val canLongPress: Boolean
509+
get() = selectedMedia.isEmpty() && !isMultiSelect
481510
val showCountButton: Boolean
482511
get() =
483512
when (countVisibility) {

app/src/main/java/org/thoughtcrime/securesms/mediasend/compose/Components.kt

Lines changed: 59 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,26 @@ import androidx.compose.foundation.layout.Spacer
1212
import androidx.compose.foundation.layout.aspectRatio
1313
import androidx.compose.foundation.layout.fillMaxSize
1414
import androidx.compose.foundation.layout.fillMaxWidth
15-
import androidx.compose.foundation.layout.height
1615
import androidx.compose.foundation.layout.padding
1716
import androidx.compose.foundation.layout.size
1817
import androidx.compose.foundation.layout.width
1918
import androidx.compose.foundation.shape.CircleShape
2019
import androidx.compose.material3.MaterialTheme
2120
import androidx.compose.material3.Text
2221
import androidx.compose.runtime.Composable
23-
import androidx.compose.runtime.remember
2422
import androidx.compose.ui.Alignment
2523
import androidx.compose.ui.Modifier
2624
import androidx.compose.ui.draw.clip
2725
import androidx.compose.ui.graphics.Brush
2826
import androidx.compose.ui.graphics.Color
2927
import androidx.compose.ui.graphics.ColorFilter
3028
import androidx.compose.ui.layout.ContentScale
31-
import androidx.compose.ui.res.dimensionResource
29+
import androidx.compose.ui.platform.LocalContext
3230
import androidx.compose.ui.res.painterResource
3331
import androidx.compose.ui.text.style.TextAlign
3432
import androidx.compose.ui.text.style.TextOverflow
3533
import androidx.compose.ui.tooling.preview.Preview
3634
import androidx.compose.ui.unit.Dp
37-
import androidx.compose.ui.unit.dp
3835
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
3936
import com.bumptech.glide.integration.compose.GlideImage
4037
import network.loki.messenger.R
@@ -43,8 +40,9 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors
4340
import org.thoughtcrime.securesms.ui.theme.LocalType
4441
import org.thoughtcrime.securesms.util.MediaUtil
4542
import kotlin.collections.filterNot
46-
import kotlin.collections.indexOfFirst
4743
import androidx.core.net.toUri
44+
import coil3.compose.AsyncImage
45+
import coil3.request.ImageRequest
4846
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
4947

5048
@OptIn(ExperimentalGlideComposeApi::class)
@@ -61,12 +59,15 @@ fun MediaFolderCell(
6159
.clickable(onClick = onClick)
6260
) {
6361
Box(modifier = Modifier.aspectRatio(1f)) {
64-
GlideImage(
65-
model = thumbnailUri,
62+
AsyncImage(
63+
modifier = Modifier.fillMaxWidth(),
64+
contentScale = ContentScale.Crop,
65+
model = ImageRequest.Builder(LocalContext.current)
66+
.data(thumbnailUri)
67+
.build(),
6668
contentDescription = null,
67-
modifier = Modifier.fillMaxSize(),
68-
contentScale = ContentScale.Crop
6969
)
70+
7071
// Bottom shade overlay
7172
Box(
7273
modifier = Modifier
@@ -125,34 +126,16 @@ fun MediaFolderCell(
125126
@Composable
126127
fun MediaPickerItemCell(
127128
media: Media,
128-
selected: List<Media>,
129-
forcedMultiSelect: Boolean,
130-
maxSelection: Int,
129+
isSelected: Boolean = false,
130+
selectedIndex: Int = 1,
131+
isMultiSelect: Boolean,
131132
onMediaChosen: (Media) -> Unit,
132133
onSelectionStarted: () -> Unit,
133-
onSelectionChanged: (List<Media>) -> Unit,
134-
onSelectionOverflow: (Int) -> Unit,
134+
onSelectionChanged: (selectedMedia: Media) -> Unit,
135135
modifier: Modifier = Modifier,
136+
showSelectionOn: Boolean = false,
137+
canLongPress: Boolean = true
136138
) {
137-
val isSelected = selected.any { it.uri == media.uri }
138-
val selectedIndex = remember(selected, media) {
139-
selected.indexOfFirst { it.uri == media.uri }
140-
}
141-
142-
// Matches adapter rules:
143-
val inSelectionUi = !(selected.isEmpty() && !forcedMultiSelect)
144-
val showSelectOff = inSelectionUi
145-
val showSelectOn = inSelectionUi && isSelected
146-
val showSelectOverlay = isSelected
147-
148-
val canStartSelectionByLongPress = maxSelection > 1 && selected.isEmpty() && !forcedMultiSelect
149-
150-
fun removeFromSelection(): List<Media> =
151-
selected.filterNot { it.uri == media.uri }
152-
153-
fun addToSelection(): List<Media> =
154-
selected + media
155-
156139
Box(
157140
modifier = modifier
158141
.aspectRatio(1f)
@@ -162,36 +145,29 @@ fun MediaPickerItemCell(
162145
)
163146
.combinedClickable(
164147
onClick = {
165-
if (selected.isEmpty() && !forcedMultiSelect) {
166-
// adapter: direct choose
167-
onMediaChosen(media)
168-
} else if (isSelected) {
169-
// adapter: remove
170-
onSelectionChanged(removeFromSelection())
148+
if (!isMultiSelect) {
149+
onMediaChosen(media) // Choosing a single media
171150
} else {
172-
// adapter: add if room else overflow
173-
if (selected.size < maxSelection) {
174-
onSelectionChanged(addToSelection())
175-
} else {
176-
onSelectionOverflow(maxSelection)
177-
}
151+
onSelectionChanged(media) // Selecting/unselecting media
178152
}
179153
},
180-
onLongClick = if (canStartSelectionByLongPress) {
154+
onLongClick = if (canLongPress) {
181155
{
182-
// adapter: long press starts selection, adds this item
183-
onSelectionChanged(listOf(media))
156+
// long press starts selection, adds this item
157+
onSelectionChanged(media)
184158
onSelectionStarted()
185159
}
186160
} else null
187161
)
188162
) {
189163
// Thumbnail
190-
GlideImage(
191-
model = media.uri,
192-
contentDescription = null,
164+
AsyncImage(
193165
modifier = Modifier.fillMaxSize(),
194-
contentScale = ContentScale.Crop
166+
contentScale = ContentScale.Crop,
167+
model = ImageRequest.Builder(LocalContext.current)
168+
.data(media.uri)
169+
.build(),
170+
contentDescription = null,
195171
)
196172

197173
// Play overlay (center) for video
@@ -214,41 +190,41 @@ fun MediaPickerItemCell(
214190
}
215191

216192
// Selection overlay
217-
if (showSelectOverlay) {
193+
if (isSelected) {
218194
Box(
219195
Modifier
220196
.matchParentSize()
221197
.background(Color.Black.copy(alpha = 0.80f))
222198
)
223199
}
224200

225-
// Select OFF badge (top-end)
226-
if (showSelectOff) {
227-
Box(
228-
modifier = Modifier
229-
.align(Alignment.TopEnd)
230-
.padding(LocalDimensions.current.xxsSpacing)
231-
) {
232-
IndicatorOff(size = LocalDimensions.current.smallRadius)
233-
}
234-
}
235-
236-
// Select ON badge + order number (top-end)
237-
if (showSelectOn) {
238-
Box(
239-
modifier = Modifier
240-
.align(Alignment.TopEnd)
241-
.padding(LocalDimensions.current.xxsSpacing),
242-
contentAlignment = Alignment.Center
243-
) {
244-
IndicatorOn(size = LocalDimensions.current.smallRadius)
201+
if (isMultiSelect) {
202+
// Select ON badge + order number (top-end)
203+
if (showSelectionOn) {
204+
Box(
205+
modifier = Modifier
206+
.align(Alignment.TopEnd)
207+
.padding(LocalDimensions.current.xxsSpacing),
208+
contentAlignment = Alignment.Center
209+
) {
210+
IndicatorOn(size = LocalDimensions.current.smallRadius)
245211

246-
Text(
247-
text = (selectedIndex + 1).toString(),
248-
color = LocalColors.current.onInvertedBackgroundAccent,
249-
style = LocalType.current.base,
250-
textAlign = TextAlign.Center
251-
)
212+
Text(
213+
text = (selectedIndex + 1).toString(),
214+
color = LocalColors.current.onInvertedBackgroundAccent,
215+
style = LocalType.current.base,
216+
textAlign = TextAlign.Center
217+
)
218+
}
219+
} else {
220+
// Select OFF badge
221+
Box(
222+
modifier = Modifier
223+
.align(Alignment.TopEnd)
224+
.padding(LocalDimensions.current.xxsSpacing)
225+
) {
226+
IndicatorOff(size = LocalDimensions.current.smallRadius)
227+
}
252228
}
253229
}
254230
}
@@ -298,13 +274,11 @@ private fun Preview_MediaPickerItemCell_NotSelected() {
298274

299275
MediaPickerItemCell(
300276
media = media,
301-
selected = emptyList(),
302-
forcedMultiSelect = false,
303-
maxSelection = 32,
277+
isMultiSelect = false,
278+
canLongPress = true,
304279
onMediaChosen = {},
305280
onSelectionStarted = {},
306281
onSelectionChanged = {},
307-
onSelectionOverflow = {},
308282
)
309283
}
310284

@@ -315,13 +289,11 @@ private fun Preview_MediaPickerItemCell_Selected() {
315289

316290
MediaPickerItemCell(
317291
media = media,
318-
selected = listOf(media), // selectedIndex = 0 -> shows "1"
319-
forcedMultiSelect = true,
320-
maxSelection = 32,
292+
isMultiSelect = true,
293+
canLongPress = true,
321294
onMediaChosen = {},
322295
onSelectionStarted = {},
323296
onSelectionChanged = {},
324-
onSelectionOverflow = {},
325297
)
326298
}
327299

0 commit comments

Comments
 (0)