tangled
alpha
login
or
join now
geesawra.industries
/
jerry-no
8
fork
atom
A cheap attempt at a native Bluesky client for Android
8
fork
atom
overview
issues
pulls
pipelines
ComposeView: allow deleting media before posting
geesawra.industries
5 months ago
af421c51
19c90922
+133
-104
3 changed files
expand all
collapse all
unified
split
app
src
main
java
industries
geesawra
monarch
ComposeView.kt
PostImageGallery.kt
SkeetView.kt
+25
-47
app/src/main/java/industries/geesawra/monarch/ComposeView.kt
···
63
63
import androidx.compose.ui.graphics.Color
64
64
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
65
65
import androidx.compose.ui.unit.dp
66
66
-
import androidx.media3.common.MimeTypes
67
66
import com.atproto.repo.StrongRef
68
67
import industries.geesawra.monarch.datalayer.SkeetData
69
68
import industries.geesawra.monarch.datalayer.TimelineViewModel
70
70
-
import io.sanghun.compose.video.RepeatMode
71
71
-
import io.sanghun.compose.video.VideoPlayer
72
72
-
import io.sanghun.compose.video.controller.VideoPlayerControllerConfig
73
73
-
import io.sanghun.compose.video.uri.VideoPlayerMediaItem
74
69
import kotlinx.coroutines.CoroutineScope
75
70
import kotlinx.coroutines.launch
76
71
···
93
88
val composeFieldState = rememberTextFieldState(
94
89
""
95
90
)
96
96
-
val mediaSelected = remember { mutableStateOf(mapOf<Uri, String?>()) }
91
91
+
val mediaSelected = remember { mutableStateOf(listOf<Uri>()) }
97
92
val mediaSelectedIsVideo = remember { mutableStateOf(false) }
98
93
99
94
LaunchedEffect(scaffoldState.bottomSheetState.isVisible) {
···
107
102
charCount.intValue = 0
108
103
inReplyTo.value = null
109
104
isQuotePost.value = false
110
110
-
mediaSelected.value = mapOf()
105
105
+
mediaSelected.value = listOf()
111
106
mediaSelectedIsVideo.value = false
112
107
113
108
}
···
120
115
true -> transferableContent.consume {
121
116
val uri = it.uri
122
117
val mimeType: String? = context.contentResolver.getType(uri)
123
123
-
mediaSelected.value = mapOf(Pair(uri, mimeType))
118
118
+
mediaSelected.value = listOf(uri)
124
119
true
125
120
}
126
121
···
169
164
}
170
165
171
166
mediaSelectedIsVideo.value = containsVideo && urisMap.size == 1
172
172
-
mediaSelected.value = urisMap.filterValues { it != null }
167
167
+
mediaSelected.value = urisMap.filterValues { it != null }.keys.toList()
173
168
}
174
169
175
170
val uploadingPost = remember { mutableStateOf(false) }
···
271
266
when (mediaSelectedIsVideo.value) {
272
267
false -> PostImageGallery(
273
268
modifier = Modifier
274
274
-
.fillMaxWidth() // Gallery should fill card width
269
269
+
.fillMaxWidth()
275
270
.padding(8.dp),
276
276
-
images = mediaSelected.value.keys.map { uri ->
271
271
+
images = mediaSelected.value.map { uri ->
277
272
Image(url = uri.toString(), alt = "Selected media")
278
273
},
274
274
+
onCrossClick = {
275
275
+
val toDelUri = mediaSelected.value[it]
276
276
+
mediaSelected.value =
277
277
+
mediaSelected.value.filter { uri ->
278
278
+
uri != toDelUri
279
279
+
}
280
280
+
}
279
281
)
280
282
281
281
-
true -> VideoPlayer(
282
282
-
mediaItems = listOf(
283
283
-
VideoPlayerMediaItem.NetworkMediaItem(
284
284
-
url = mediaSelected.value.keys.first().toString(),
285
285
-
mimeType = MimeTypes.APPLICATION_M3U8,
286
286
-
)
287
287
-
),
288
288
-
handleLifecycle = false,
289
289
-
autoPlay = false,
290
290
-
usePlayerController = true,
291
291
-
enablePip = false,
292
292
-
handleAudioFocus = true,
293
293
-
controllerConfig = VideoPlayerControllerConfig(
294
294
-
showSpeedAndPitchOverlay = false,
295
295
-
showSubtitleButton = false,
296
296
-
showCurrentTimeAndTotalTime = true,
297
297
-
showBufferingProgress = false,
298
298
-
showForwardIncrementButton = true,
299
299
-
showBackwardIncrementButton = true,
300
300
-
showBackTrackButton = false,
301
301
-
showNextTrackButton = false,
302
302
-
showRepeatModeButton = true,
303
303
-
controllerShowTimeMilliSeconds = 5_000,
304
304
-
controllerAutoShow = true,
305
305
-
showFullScreenButton = true,
306
306
-
),
307
307
-
volume = 0.5f, // volume 0.0f to 1.0f
308
308
-
repeatMode = RepeatMode.NONE, // or RepeatMode.ALL, RepeatMode.ONE
309
309
-
modifier = Modifier
310
310
-
.fillMaxSize()
311
311
-
.heightIn(max = 500.dp)
312
312
-
.padding(8.dp),
313
313
-
)
283
283
+
true -> DeletableMediaView(
284
284
+
originalIndex = 0,
285
285
+
onCrossClick = {
286
286
+
mediaSelected.value = listOf()
287
287
+
},
288
288
+
onMediaClick = { }
289
289
+
) {
290
290
+
VideoView(uri = mediaSelected.value.first())
291
291
+
}
314
292
}
315
293
}
316
294
}
···
329
307
uploadingPost: MutableState<Boolean>,
330
308
pickMedia: ManagedActivityResultLauncher<PickVisualMediaRequest, List<@JvmSuppressWildcards Uri>>,
331
309
postText: String,
332
332
-
mediaSelected: MutableState<Map<Uri, String?>>,
310
310
+
mediaSelected: MutableState<List<Uri>>,
333
311
mediaSelectedIsVideo: MutableState<Boolean>,
334
312
coroutineScope: CoroutineScope,
335
313
maxChars: Int,
···
371
349
uploadingPost.value = true // Show progress immediately
372
350
timelineViewModel.post(
373
351
content = postText,
374
374
-
images = if (!mediaSelectedIsVideo.value) mediaSelected.value.keys.toList()
352
352
+
images = if (!mediaSelectedIsVideo.value) mediaSelected.value
375
353
.ifEmpty { null } else null,
376
376
-
video = if (mediaSelectedIsVideo.value) mediaSelected.value.keys.firstOrNull() else null,
354
354
+
video = if (mediaSelectedIsVideo.value) mediaSelected.value.firstOrNull() else null,
377
355
replyRef = if (!isQuotePost) {
378
356
inReplyToData?.replyRef()
379
357
} else {
+107
-56
app/src/main/java/industries/geesawra/monarch/PostImageGallery.kt
···
2
2
3
3
import androidx.compose.foundation.clickable
4
4
import androidx.compose.foundation.layout.Arrangement
5
5
+
import androidx.compose.foundation.layout.Box
5
6
import androidx.compose.foundation.layout.Column
6
6
-
import androidx.compose.foundation.layout.IntrinsicSize // Added import
7
7
+
import androidx.compose.foundation.layout.IntrinsicSize
7
8
import androidx.compose.foundation.layout.Row
8
8
-
import androidx.compose.foundation.layout.RowScope
9
9
import androidx.compose.foundation.layout.Spacer
10
10
import androidx.compose.foundation.layout.aspectRatio
11
11
-
import androidx.compose.foundation.layout.fillMaxHeight // Added import
11
11
+
import androidx.compose.foundation.layout.fillMaxHeight
12
12
import androidx.compose.foundation.layout.fillMaxWidth
13
13
-
import androidx.compose.foundation.layout.height // Added import
13
13
+
import androidx.compose.foundation.layout.height
14
14
+
import androidx.compose.foundation.layout.padding
14
15
import androidx.compose.foundation.shape.RoundedCornerShape
16
16
+
import androidx.compose.material.icons.Icons
17
17
+
import androidx.compose.material.icons.filled.Close
18
18
+
import androidx.compose.material3.Button
19
19
+
import androidx.compose.material3.Icon
15
20
import androidx.compose.runtime.Composable
16
21
import androidx.compose.runtime.mutableStateOf
17
22
import androidx.compose.runtime.remember
···
33
38
fun PostImageGallery(
34
39
modifier: Modifier = Modifier,
35
40
images: List<Image>,
41
41
+
onCrossClick: ((Int) -> Unit)? = null
36
42
) {
37
43
val galleryVisible = remember { mutableStateOf<Int?>(null) }
38
44
···
60
66
modifier = modifier
61
67
.fillMaxWidth()
62
68
) {
63
63
-
AsyncImage(
64
64
-
model = ImageRequest.Builder(LocalContext.current)
65
65
-
.data(imagesToDisplay[0].url)
66
66
-
.crossfade(true)
67
67
-
.build(),
68
68
-
contentDescription = imagesToDisplay[0].alt,
69
69
-
contentScale = ContentScale.Crop,
70
70
-
modifier = Modifier
71
71
-
.fillMaxWidth()
72
72
-
.aspectRatio(1f) // Added aspect ratio for defined height
73
73
-
.clip(RoundedCornerShape(12.dp))
74
74
-
.clickable { galleryVisible.value = 0 } // Index in original list
75
75
-
)
69
69
+
DeletableImageView(
70
70
+
modifier = Modifier.weight(1f),
71
71
+
image = imagesToDisplay[0],
72
72
+
originalIndex = 0,
73
73
+
onCrossClick = onCrossClick,
74
74
+
onMediaClick = { galleryVisible.value = 0 })
76
75
}
77
76
}
78
77
···
81
80
modifier = modifier.fillMaxWidth(),
82
81
horizontalArrangement = Arrangement.spacedBy(8.dp)
83
82
) {
84
84
-
GalleryImageCell(
83
83
+
DeletableImageView(
84
84
+
modifier = Modifier.weight(1f),
85
85
image = imagesToDisplay[0],
86
86
originalIndex = 0,
87
87
-
onImageClick = { galleryVisible.value = it })
88
88
-
GalleryImageCell(
87
87
+
onCrossClick = onCrossClick,
88
88
+
onMediaClick = { galleryVisible.value = it })
89
89
+
DeletableImageView(
90
90
+
modifier = Modifier.weight(1f),
91
91
+
89
92
image = imagesToDisplay[1],
90
93
originalIndex = 1,
91
91
-
onImageClick = { galleryVisible.value = it })
94
94
+
onCrossClick = onCrossClick,
95
95
+
onMediaClick = { galleryVisible.value = it })
92
96
}
93
97
}
94
98
···
101
105
modifier = Modifier.fillMaxWidth(),
102
106
horizontalArrangement = Arrangement.spacedBy(8.dp)
103
107
) {
104
104
-
GalleryImageCell(
108
108
+
DeletableImageView(
109
109
+
modifier = Modifier.weight(1f),
105
110
image = imagesToDisplay[0],
106
111
originalIndex = 0,
107
107
-
onImageClick = { galleryVisible.value = it })
108
108
-
GalleryImageCell(
112
112
+
onCrossClick = onCrossClick,
113
113
+
onMediaClick = { galleryVisible.value = it })
114
114
+
DeletableImageView(
115
115
+
modifier = Modifier.weight(1f),
109
116
image = imagesToDisplay[1],
110
117
originalIndex = 1,
111
111
-
onImageClick = { galleryVisible.value = it })
118
118
+
onCrossClick = onCrossClick,
119
119
+
onMediaClick = { galleryVisible.value = it })
112
120
}
113
121
Row(
114
122
modifier = Modifier
···
116
124
.height(IntrinsicSize.Min), // Apply IntrinsicSize.Min to the Row
117
125
horizontalArrangement = Arrangement.spacedBy(8.dp)
118
126
) {
119
119
-
GalleryImageCell(
127
127
+
DeletableImageView(
128
128
+
modifier = Modifier.weight(1f),
120
129
image = imagesToDisplay[2],
121
130
originalIndex = 2,
122
122
-
onImageClick = { galleryVisible.value = it })
131
131
+
onCrossClick = onCrossClick,
132
132
+
onMediaClick = { galleryVisible.value = it })
123
133
Spacer(
124
134
Modifier
125
135
.weight(1f)
···
138
148
modifier = Modifier.fillMaxWidth(),
139
149
horizontalArrangement = Arrangement.spacedBy(8.dp)
140
150
) {
141
141
-
GalleryImageCell(
151
151
+
DeletableImageView(
152
152
+
modifier = Modifier.weight(1f),
142
153
image = imagesToDisplay[0],
143
154
originalIndex = 0,
144
144
-
onImageClick = { galleryVisible.value = it })
145
145
-
GalleryImageCell(
155
155
+
onCrossClick = onCrossClick,
156
156
+
onMediaClick = { galleryVisible.value = it })
157
157
+
DeletableImageView(
158
158
+
modifier = Modifier.weight(1f),
146
159
image = imagesToDisplay[1],
147
160
originalIndex = 1,
148
148
-
onImageClick = { galleryVisible.value = it })
161
161
+
onCrossClick = onCrossClick,
162
162
+
onMediaClick = { galleryVisible.value = it })
149
163
}
150
164
Row(
151
165
modifier = Modifier.fillMaxWidth(),
152
166
horizontalArrangement = Arrangement.spacedBy(8.dp)
153
167
) {
154
154
-
GalleryImageCell(
168
168
+
DeletableImageView(
169
169
+
modifier = Modifier.weight(1f),
155
170
image = imagesToDisplay[2],
156
171
originalIndex = 2,
157
157
-
onImageClick = { galleryVisible.value = it })
158
158
-
GalleryImageCell(
172
172
+
onCrossClick = onCrossClick,
173
173
+
onMediaClick = { galleryVisible.value = it })
174
174
+
DeletableImageView(
175
175
+
modifier = Modifier.weight(1f),
159
176
image = imagesToDisplay[3],
160
177
originalIndex = 3,
161
161
-
onImageClick = { galleryVisible.value = it })
178
178
+
onCrossClick = onCrossClick,
179
179
+
onMediaClick = { galleryVisible.value = it })
162
180
}
163
181
}
164
182
}
···
166
184
}
167
185
168
186
@Composable
169
169
-
private fun RowScope.GalleryImageCell(
187
187
+
private fun DeletableImageView(
188
188
+
modifier: Modifier = Modifier,
170
189
image: Image,
171
171
-
originalIndex: Int, // Index in the original `images` list
172
172
-
onImageClick: (Int) -> Unit
190
190
+
originalIndex: Int,
191
191
+
onCrossClick: ((Int) -> Unit)? = null,
192
192
+
onMediaClick: (Int) -> Unit,
193
193
+
) {
194
194
+
DeletableMediaView(
195
195
+
modifier = modifier,
196
196
+
originalIndex = originalIndex,
197
197
+
onCrossClick = onCrossClick,
198
198
+
onMediaClick = onMediaClick,
199
199
+
) {
200
200
+
AsyncImage(
201
201
+
model = ImageRequest.Builder(LocalContext.current)
202
202
+
.data(image.url)
203
203
+
.crossfade(true)
204
204
+
.build(),
205
205
+
contentDescription = image.alt,
206
206
+
contentScale = ContentScale.Crop,
207
207
+
modifier = Modifier
208
208
+
.aspectRatio(1f) // Changed from fillMaxSize() to make it square
209
209
+
.clip(RoundedCornerShape(12.dp))
210
210
+
.clickable { onMediaClick(originalIndex) }
211
211
+
)
212
212
+
}
213
213
+
}
214
214
+
215
215
+
@Composable
216
216
+
fun DeletableMediaView(
217
217
+
modifier: Modifier = Modifier,
218
218
+
originalIndex: Int,
219
219
+
onCrossClick: ((Int) -> Unit)? = null,
220
220
+
onMediaClick: (Int) -> Unit,
221
221
+
mediaView: @Composable () -> Unit,
173
222
) {
174
174
-
AsyncImage(
175
175
-
model = ImageRequest.Builder(LocalContext.current)
176
176
-
.data(image.url)
177
177
-
.crossfade(true)
178
178
-
.build(),
179
179
-
contentDescription = image.alt,
180
180
-
contentScale = ContentScale.Crop,
181
181
-
modifier = Modifier
182
182
-
.weight(1f)
223
223
+
Box(
224
224
+
modifier = modifier
183
225
.aspectRatio(1f) // Changed from fillMaxSize() to make it square
184
226
.clip(RoundedCornerShape(12.dp))
185
185
-
.clickable { onImageClick(originalIndex) }
186
186
-
)
187
187
-
}
227
227
+
.clickable { onMediaClick(originalIndex) }
228
228
+
) {
188
229
189
189
-
// Placeholder for GalleryViewer - ensure it's defined elsewhere
190
190
-
/*
191
191
-
@Composable
192
192
-
fun GalleryViewer(imageUrls: List<Image>, initialPage: Int, onDismiss: () -> Unit) {
193
193
-
// ... your GalleryViewer implementation ...
230
230
+
mediaView()
231
231
+
232
232
+
if (onCrossClick != null) {
233
233
+
Button(
234
234
+
modifier = Modifier.padding(start = 8.dp, top = 4.dp),
235
235
+
onClick = {
236
236
+
onCrossClick(originalIndex)
237
237
+
}
238
238
+
) {
239
239
+
Icon(
240
240
+
imageVector = Icons.Default.Close,
241
241
+
contentDescription = "Remove embed",
242
242
+
)
243
243
+
}
244
244
+
}
245
245
+
}
194
246
}
195
195
-
*/
+1
-1
app/src/main/java/industries/geesawra/monarch/SkeetView.kt
···
220
220
}
221
221
222
222
@Composable
223
223
-
private fun VideoView(uri: Uri) {
223
223
+
fun VideoView(uri: Uri) {
224
224
Card(
225
225
modifier = Modifier
226
226
.heightIn(max = 500.dp)