Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Feedback form attachments #21114

Merged
merged 68 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
f1a13b1
Copied some basic attachment code from DOAndroid
nbradbury Aug 1, 2024
c246e15
Added basic attachments section and photo picker
nbradbury Aug 1, 2024
3710c95
Rewrote preview
nbradbury Aug 1, 2024
e29cac9
Fixed attachment button
nbradbury Aug 1, 2024
d8dd260
Removed separate attachment section
nbradbury Aug 1, 2024
07ee28f
Added attachment to preview, added MediaBrowserType.FEEDBACK_FORM_SIN…
nbradbury Aug 1, 2024
8383a73
Cleaned up FeedbackFormScreenPreview
nbradbury Aug 1, 2024
bc42af8
Fixed ReturnCount warning in addAttachment
nbradbury Aug 1, 2024
e5da5d9
Fixed detekt warnings in Uri.copyToTempFile
nbradbury Aug 1, 2024
7ab87bd
Fixed remaining detekt warnings in UriExt
nbradbury Aug 1, 2024
b3ecb05
Added comments to UriExt
nbradbury Aug 1, 2024
1e20148
Add single selected media attachments
nbradbury Aug 1, 2024
5773352
Allow multi select
nbradbury Aug 1, 2024
99eada1
Merge branch 'feature/feedback-form-attachments' of https://github.co…
nbradbury Aug 1, 2024
1a32b3d
Always use showToast fun
nbradbury Aug 1, 2024
00a98df
Fixed checkstyle issue with FeedbackFormUtils
nbradbury Aug 1, 2024
cfd656a
Fixed failing test
nbradbury Aug 1, 2024
bac9c1a
Simplified attachment types to just image & video
nbradbury Aug 2, 2024
c973e6d
Include attachments in discard confirmation
nbradbury Aug 2, 2024
554c512
Merge pull request #21107 from wordpress-mobile/issue/21105-feedback-…
nbradbury Aug 2, 2024
9b5164f
Added attachment IDs
nbradbury Aug 2, 2024
a624caa
First pass at ZendeskUploadHelper
nbradbury Aug 2, 2024
4d42520
Fixed warnings
nbradbury Aug 5, 2024
398c6a6
Make attachmentIds optional
nbradbury Aug 5, 2024
46357d7
Merge branch 'trunk' into feature/feedback-form-attachments
nbradbury Aug 5, 2024
bd19684
Merge branch 'feature/feedback-form-attachments' of https://github.co…
nbradbury Aug 5, 2024
be10c4f
Upload multiple attachments
nbradbury Aug 5, 2024
4dc33ab
Pass attachment IDs when creating request
nbradbury Aug 5, 2024
490dcf2
Merge branch 'trunk' into feature/feedback-form-attachments
nbradbury Aug 6, 2024
2887a7c
Merge branch 'feature/feedback-form-attachments' of https://github.co…
nbradbury Aug 6, 2024
5bd4a3c
Moved uploading attachments to the view model
nbradbury Aug 6, 2024
d1f9d5e
Updated to use attachment tokens
nbradbury Aug 6, 2024
c084d4a
Use tempfile rather than uri for upload
nbradbury Aug 6, 2024
c8c55af
Use a completion handler to await uploads
nbradbury Aug 6, 2024
9bbb3a8
Minor cleanup
nbradbury Aug 6, 2024
765f77f
Fixed checkstyle warnings
nbradbury Aug 6, 2024
e4fc976
Merge pull request #21115 from wordpress-mobile/issue/21105-feedback-…
nbradbury Aug 7, 2024
0c6435e
Improved comment
nbradbury Aug 7, 2024
a35bdab
Added simple progress dialog
nbradbury Aug 7, 2024
6d577c2
Simplified progress dialog
nbradbury Aug 7, 2024
b0f0ba9
Use a progress dialog during uploading
nbradbury Aug 7, 2024
5646a61
Don't allow progress to be dismissed
nbradbury Aug 7, 2024
1a0c678
First pass at suspend upload
nbradbury Aug 7, 2024
7dd3182
Second pass at uploading in a coroutine
nbradbury Aug 7, 2024
2277536
Merge pull request #21116 from wordpress-mobile/issue/21105-feedback-…
nbradbury Aug 7, 2024
a6107a8
Removed unused fun
nbradbury Aug 7, 2024
a23996e
Merge branch 'feature/feedback-form-attachments' into issue/21105-fee…
nbradbury Aug 7, 2024
74f9de7
Fixed checkstyle error
nbradbury Aug 7, 2024
704e74e
Reverted to suspendCoroutine
nbradbury Aug 7, 2024
25e675b
Merge pull request #21117 from wordpress-mobile/issue/21105-feedback-…
nbradbury Aug 7, 2024
e3d7f84
Make sure continuation is active before resuming
nbradbury Aug 7, 2024
13413b0
Removed redundant logging
nbradbury Aug 7, 2024
bb4e62a
Fixed checkstyle issue
nbradbury Aug 7, 2024
bca0784
Merge branch 'feature/feedback-form-attachments' of https://github.co…
nbradbury Aug 7, 2024
8537ceb
Improved preview
nbradbury Aug 7, 2024
49e1886
Minor preview change
nbradbury Aug 7, 2024
85dba34
Don't continue adding attachments when one fails
nbradbury Aug 7, 2024
bfa6f18
Changed MAX_SINGLE_ATTACHMENT_SIZE to match iOS
nbradbury Aug 7, 2024
26ab4dc
Removed max total attachment size to match iOS
nbradbury Aug 7, 2024
a6b7358
Changed MAX_ATTACHMENTS to 5 to match iOS
nbradbury Aug 7, 2024
308ba00
Don't hard-code the size limit in the string resource
nbradbury Aug 7, 2024
6602624
Fixed failing test
nbradbury Aug 7, 2024
57cfc7e
Merge pull request #21119 from wordpress-mobile/issue/21105-feedback-…
nbradbury Aug 7, 2024
9d70667
Merge branch 'trunk' into feature/feedback-form-attachments
nbradbury Aug 8, 2024
45bf19c
Auto-focus the message field
nbradbury Aug 8, 2024
ddfb08b
Fix file:// URIs
nbradbury Aug 8, 2024
2f62673
Corrected typo in comment
nbradbury Aug 8, 2024
bd868ee
Use KeyboardCapitalization.Sentences
nbradbury Aug 8, 2024
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
4 changes: 1 addition & 3 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ class ZendeskHelper(
selectedSite: SiteModel?,
extraTags: List<String>?,
requestDescription: String,
callback: CreateRequestCallback
callback: CreateRequestCallback,
attachmentTokens: List<String> = emptyList()
) {
require(isZendeskEnabled) {
zendeskNeedsToBeEnabledError
Expand All @@ -186,6 +187,7 @@ class ZendeskHelper(
ticketFormId = TicketFieldIds.form
tags = buildZendeskTags(siteStore.sites, selectedSite, origin ?: Origin.UNKNOWN, extraTags)
.plus("DocsBot")
attachments = attachmentTokens
}

Support.INSTANCE.provider()?.requestProvider()?.createRequest(request, object : ZendeskCallback<Request>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.wordpress.android.support

import com.zendesk.service.ErrorResponse
import com.zendesk.service.ZendeskCallback
import kotlinx.coroutines.suspendCancellableCoroutine
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T
import org.wordpress.android.util.extensions.mimeType
import zendesk.support.Support
import zendesk.support.UploadResponse
import java.io.File
import java.io.IOException
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
* https://zendesk.github.io/mobile_sdk_javadocs/supportv2/v301/index.html?zendesk/support/UploadProvider.html
*/
class ZendeskUploadHelper @Inject constructor() {
/**
* Uploads multiple file attachments to Zendesk and returns a list of their tokens when
* all uploads have completed
*/
suspend fun uploadFileAttachments(
files: List<File>
) = suspendCancellableCoroutine { continuation ->
val uploadProvider = Support.INSTANCE.provider()?.uploadProvider()
if (uploadProvider == null) {
continuation.resumeWithException(IOException("Unable to upload attachments (null provider)"))
return@suspendCancellableCoroutine
}

val tokens = ArrayList<String>()
var numAttachments = files.size

val callback = object : ZendeskCallback<UploadResponse>() {
override fun onSuccess(result: UploadResponse) {
if (continuation.isActive) {
result.token?.let {
tokens.add(it)
}
numAttachments--
if (numAttachments <= 0) {
continuation.resume(tokens)
}
}
}

override fun onError(errorResponse: ErrorResponse?) {
if (continuation.isActive) {
continuation.resumeWithException(
IOException("Uploading to Zendesk failed with ${errorResponse?.reason}")
)
}
}
}
for (file in files) {
uploadProvider.uploadAttachment(
file.name,
file,
file.mimeType() ?: "",
callback
)
}
}

/**
* Deletes an attachment from Zendesk. This is currently used only during development.
*/
@Suppress("unused")
fun deleteAttachment(token: String) {
val uploadProvider = Support.INSTANCE.provider()?.uploadProvider()
if (uploadProvider == null) {
AppLog.e(T.SUPPORT, "Upload provider is null")
return
}
uploadProvider.deleteAttachment(token, object : ZendeskCallback<Void>() {
override fun onSuccess(result: Void) {
AppLog.i(T.SUPPORT, "Successfully deleted Zendesk attachment")
}

override fun onError(errorResponse: ErrorResponse?) {
AppLog.e(T.SUPPORT, "Unable to delete Zendesk attachment: ${errorResponse?.reason}")
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.wordpress.android.ui.compose.components

import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import kotlinx.coroutines.flow.Flow
import org.wordpress.android.R
import org.wordpress.android.ui.compose.theme.M3Theme

@Composable
fun ProgressDialog(progressDialogState: Flow<ProgressDialogState?>) {
progressDialogState.collectAsState(initial = null).value?.let {
ProgressDialog(progressDialogState = it)
}
}

@Composable
fun ProgressDialog(progressDialogState: ProgressDialogState) {
M3Theme {
val dialogProps = DialogProperties(
dismissOnBackPress = progressDialogState.dismissible,
dismissOnClickOutside = progressDialogState.dismissible,
usePlatformDefaultWidth = false
)
Dialog(onDismissRequest = progressDialogState.onDismiss, dialogProps) {
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp))
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.defaultMinSize(100.dp, 100.dp)
) {
if (progressDialogState.progress != null) {
CircularProgressIndicator(progress = {
progressDialogState.progress
}, Modifier.padding(16.dp))
} else {
CircularProgressIndicator(Modifier.padding(16.dp))
}
if (progressDialogState.message != null) {
Text(
text = LocalContext.current.getString(progressDialogState.message),
Modifier.padding(16.dp, 0.dp, 16.dp, 16.dp),
color = MaterialTheme.colorScheme.onSurface
)
}
if (progressDialogState.showCancel) {
OutlinedButton(
onClick = progressDialogState.onDismiss,
shape = RoundedCornerShape(20.dp),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)),
modifier = Modifier.padding(16.dp, 0.dp, 16.dp, 16.dp)
) {
Text(
text = stringResource(id = R.string.cancel),
Modifier.padding(2.dp),
color = MaterialTheme.colorScheme.onSurface
)
}
}
}
}
}
}
}

data class ProgressDialogState(
@StringRes val message: Int? = null,
val progress: Float? = null,
val showCancel: Boolean = false,
val dismissible: Boolean = true,
val onDismiss: () -> Unit = {}
)

@Preview(
name = "Light Mode",
showBackground = true
)
@Preview(
name = "Dark Mode",
showBackground = true,
uiMode = Configuration.UI_MODE_NIGHT_YES,
)
@Composable
fun ProgressDialogPreview() {
ProgressDialog(
progressDialogState = ProgressDialogState(
message = R.string.uploading,
showCancel = true,
progress = 50f / 100f,
)
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.wordpress.android.ui.main.feedbackform

import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.viewModels
Expand All @@ -8,6 +9,7 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import dagger.hilt.android.AndroidEntryPoint
import org.wordpress.android.ui.LocaleAwareActivity
import org.wordpress.android.ui.RequestCodes

@AndroidEntryPoint
class FeedbackFormActivity : LocaleAwareActivity() {
Expand All @@ -25,7 +27,8 @@ class FeedbackFormActivity : LocaleAwareActivity() {
setContent {
FeedbackFormScreen(
messageText = viewModel.messageText.collectAsState(),
isProgressShowing = viewModel.isProgressShowing.collectAsState(),
progressDialogState = viewModel.progressDialogState.collectAsState(),
attachments = viewModel.attachments.collectAsState(),
onMessageChanged = {
viewModel.updateMessageText(it)
},
Expand All @@ -34,10 +37,27 @@ class FeedbackFormActivity : LocaleAwareActivity() {
},
onCloseClick = {
viewModel.onCloseClick(this@FeedbackFormActivity)
},
onChooseMediaClick = {
viewModel.onChooseMediaClick(this@FeedbackFormActivity)
},
onRemoveMediaClick = {
viewModel.onRemoveMediaClick(it)
}
)
}
}
)
}

@Deprecated("Deprecated in Java")
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (requestCode == RequestCodes.PHOTO_PICKER) {
data?.let {
viewModel.onPhotoPickerResult(this, it)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.wordpress.android.ui.main.feedbackform

import android.net.Uri
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,
)

enum class FeedbackFormAttachmentType {
IMAGE,
VIDEO,
}
Loading
Loading