Skip to content

Commit

Permalink
Merge pull request #21121 from wordpress-mobile/issue/21105-feedback-…
Browse files Browse the repository at this point in the history
…image-pager

Feedback form: attachment pager
  • Loading branch information
nbradbury authored Aug 9, 2024
2 parents 206f726 + df91b9a commit 1290c89
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 126 deletions.
28 changes: 15 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ GEM
ast (2.4.2)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.962.0)
aws-partitions (1.960.0)
aws-sdk-core (3.201.3)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
Expand All @@ -30,7 +30,7 @@ GEM
aws-sdk-kms (1.88.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.157.0)
aws-sdk-s3 (1.156.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
Expand All @@ -55,7 +55,7 @@ GEM
connection_pool (2.4.1)
cork (0.3.0)
colored2 (~> 3.1)
danger (9.5.0)
danger (9.4.3)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
Expand All @@ -65,9 +65,10 @@ GEM
git (~> 1.13)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (>= 4.0)
terminal-table (>= 1, < 4)
danger-dangermattic (1.1.2)
danger-dangermattic (1.1.1)
danger (~> 9.4)
danger-plugin-api (~> 1.0)
danger-rubocop (~> 0.13)
Expand Down Expand Up @@ -158,7 +159,7 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-sentry (1.24.0)
fastlane-plugin-sentry (1.19.0)
os (~> 1.1, >= 1.1.4)
fastlane-plugin-wpmreleasetoolkit (11.1.0)
activesupport (>= 6.1.7.1)
Expand Down Expand Up @@ -197,7 +198,7 @@ GEM
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
Expand Down Expand Up @@ -244,6 +245,7 @@ GEM
nap (1.1.0)
naturally (2.2.1)
nkf (0.2.0)
no_proxy_fix (0.1.2)
nokogiri (1.16.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
Expand All @@ -255,7 +257,7 @@ GEM
optparse (0.5.0)
os (1.1.4)
parallel (1.25.1)
parser (3.3.4.1)
parser (3.3.2.0)
ast (~> 2.4.1)
racc
plist (3.7.1)
Expand All @@ -275,22 +277,22 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.4)
rexml (3.2.9)
strscan
rmagick (4.3.0)
rouge (2.0.7)
rubocop (1.65.1)
rubocop (1.64.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.32.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
Expand Down Expand Up @@ -321,13 +323,13 @@ GEM
uber (0.1.0)
unicode-display_width (2.5.0)
word_wrap (1.0.0)
xcodeproj (1.25.0)
xcodeproj (1.24.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (>= 3.3.2, < 4.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
Expand Down
1 change: 1 addition & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout-compose:$androidxConstraintlayoutComposeVersion"
// - Jetpack Compose - Other
implementation "io.coil-kt:coil-compose:$coilComposeVersion"
implementation "io.coil-kt:coil-video:$coilComposeVersion"
implementation "com.airbnb.android:lottie-compose:$lottieVersion"
// - Jetpack Compose - UI Tests
androidTestImplementation "androidx.compose.ui:ui-test-junit4"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package org.wordpress.android.ui.compose.components

import android.content.res.Configuration
import android.net.Uri
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PageSize
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.decode.VideoFrameDecoder
import coil.request.ImageRequest
import org.wordpress.android.R

/**
* A simple pager to show a carousel from a list of local media URIs. This was designed
* to show feedback form attachments but should be suitable for other use cases.
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MediaUriPager(
mediaUris: List<Uri>,
modifier: Modifier = Modifier,
showButton: Boolean = true,
onButtonClick: (Uri) -> Unit = {},
) {
if (mediaUris.isEmpty()) {
return
}
val pagerState = rememberPagerState(
pageCount = { mediaUris.size }
)
HorizontalPager(
state = pagerState,
pageSpacing = 12.dp,
pageSize = PageSize.Fixed(IMAGE_SIZE.dp),
modifier = Modifier.then(modifier)
) { index ->
val uri = mediaUris[index]
Box(
modifier = Modifier.height(IMAGE_SIZE.dp),
) {
MediaUriImage(uri)
if (showButton) {
ImageButton(
uri = uri,
itemNumber = index + 1,
onButtonClick = onButtonClick
)
}
}
}
}

@Composable
private fun MediaUriImage(uri: Uri) {
val context = LocalContext.current
val mimeType = context.contentResolver.getType(uri)
if (mimeType?.startsWith("video/") == true) {
val imageLoader = ImageLoader.Builder(LocalContext.current)
.components {
add(VideoFrameDecoder.Factory())
}
.build()
Box {
AsyncImage(
model = ImageRequest.Builder(context)
.data(uri)
.crossfade(true)
.build(),
imageLoader = imageLoader,
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier
.border(1.dp, MaterialTheme.colorScheme.onBackground.copy(alpha = 0.25f))
.size(IMAGE_SIZE.dp)
)
Image(
imageVector = ImageVector.vectorResource(id = org.wordpress.android.editor.R.drawable.ic_overlay_video),
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.size(36.dp)
)
}
} else {
AsyncImage(
model = ImageRequest.Builder(context)
.data(uri)
.crossfade(true)
.placeholder(R.color.placeholder)
.error(org.wordpress.android.editor.R.drawable.ic_image_failed_grey_a_40_48dp)
.build(),
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier
.border(1.dp, MaterialTheme.colorScheme.onBackground.copy(alpha = 0.25f))
.size(IMAGE_SIZE.dp)
)
}
}

@Composable
private fun BoxScope.ImageButton(
uri: Uri,
itemNumber: Int,
onButtonClick: (Uri) -> Unit = {},
) {
IconButton(
onClick = { onButtonClick(uri) },
modifier = Modifier
.absoluteOffset(x = (-2).dp, y = (-2).dp)
.background(
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.7f),
shape = RoundedCornerShape(2.dp)
)
.size(24.dp)
.align(Alignment.BottomEnd),
) {
Icon(
imageVector = Icons.Filled.Close,
tint = MaterialTheme.colorScheme.onSurface,
contentDescription = stringResource(
R.string.media_pager_remove_item_content_description,
itemNumber
)
)
}
}

@Preview(
name = "Light Mode",
showBackground = true
)
@Preview(
name = "Dark Mode",
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_YES,
)
@Composable
private fun MediaPagerPreview() {
val attachment1 = Uri.parse("/tmp/attachment.jpg")
val attachment2 = Uri.parse("/tmp/attachment.mp4")
MediaUriPager(
mediaUris = listOf(attachment1, attachment2)
)
}

private const val IMAGE_SIZE = 128
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import java.io.File
data class FeedbackFormAttachment(
val uri: Uri,
val tempFile: File,
val displayName: String,
val mimeType: String,
val attachmentType: FeedbackFormAttachmentType,
val size: Long,
Expand Down
Loading

0 comments on commit 1290c89

Please sign in to comment.