Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions demo-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
android:scheme="wp-oauth-test" />
</intent-filter>
</activity>
<provider
android:name=".DemoFileProvider"
android:authorities="${applicationId}.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gravatar.demoapp

import androidx.core.content.FileProvider

internal class DemoFileProvider : FileProvider(R.xml.filepaths)
6 changes: 6 additions & 0 deletions demo-app/src/main/res/xml/filepaths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="demoapp_images"
path="demoapp/" />
</paths>
9 changes: 9 additions & 0 deletions gravatar-quickeditor/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
android:name="com.gravatar.quickeditor.initializer.QuickEditorContainerInitializer"
android:value="androidx.startup" />
</provider>
<provider
android:name=".QuickEditorFileProvider"
android:authorities="${applicationId}.com.quickeditor.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/quickeditor_filepaths" />
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.gravatar.quickeditor

import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import java.io.File

internal class QuickEditorFileProvider : FileProvider(R.xml.quickeditor_filepaths) {
companion object {
fun getTempCameraImageUri(context: Context): Uri {
val directory = File(context.cacheDir, "quickeditor")
directory.mkdirs()
val file = File(directory, "temp_camera_image.jpg")
val authority = "${context.packageName}.com.quickeditor.fileprovider"
return getUriForFile(
context,
authority,
file,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import java.io.File
internal class FileUtils(
private val context: Context,
) {
fun createTempFile(): File {
return File(context.cacheDir, "gravatar_${System.currentTimeMillis()}")
fun createCroppedAvatarFile(): File {
return File(context.cacheDir, "cropped_avatar_${System.currentTimeMillis()}.jpg")
}

fun deleteFile(uri: Uri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ internal class AvatarPickerViewModel(

fun localImageSelected(imageUri: Uri) {
viewModelScope.launch {
_actions.send(AvatarPickerAction.LaunchImageCropper(imageUri, fileUtils.createTempFile()))
_actions.send(AvatarPickerAction.LaunchImageCropper(imageUri, fileUtils.createCroppedAvatarFile()))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.gravatar.quickeditor.QuickEditorFileProvider
import com.gravatar.quickeditor.R
import com.gravatar.quickeditor.ui.avatarpicker.AvatarUi
import com.gravatar.quickeditor.ui.avatarpicker.AvatarsSectionUiState
Expand All @@ -50,14 +52,21 @@ internal fun AvatarsSection(
onLocalImageSelected: (Uri) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
var popupVisible by rememberSaveable { mutableStateOf(false) }
var popupYOffset by rememberSaveable { mutableIntStateOf(0) }
var photoImageUri by rememberSaveable { mutableStateOf<Uri?>(null) }
val listState = rememberLazyListState()

val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let { onLocalImageSelected(it) }
}

val takePhoto = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { success ->
val takenPictureUri = photoImageUri
if (success && takenPictureUri != null) onLocalImageSelected(takenPictureUri)
}

LaunchedEffect(state.scrollToIndex) {
state.scrollToIndex?.let { listState.scrollToItem(it) }
}
Expand Down Expand Up @@ -133,7 +142,12 @@ internal fun AvatarsSection(
popupVisible = false
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
},
onTakePhotoClick = { popupVisible = false },
onTakePhotoClick = {
popupVisible = false
val imageUri = QuickEditorFileProvider.getTempCameraImageUri(context)
photoImageUri = imageUri
takePhoto.launch(imageUri)
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import com.yalantis.ucrop.UCrop
import com.yalantis.ucrop.UCropActivity
import java.io.File

private const val UCROP_COMPRESSION_QUALITY = 100
private const val UCROP_COMPRESSION_QUALITY = 75
private const val UCROP_MAX_IMAGE_SIZE = 1080

internal class UCropCropperLauncher : CropperLauncher {
override fun launch(launcher: ActivityResultLauncher<Intent>, image: Uri, tempFile: File, context: Context) {
Expand All @@ -19,6 +20,7 @@ internal class UCropCropperLauncher : CropperLauncher {
setToolbarWidgetColor(Color.WHITE)
setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.NONE)
setCompressionQuality(UCROP_COMPRESSION_QUALITY)
withMaxResultSize(UCROP_MAX_IMAGE_SIZE, UCROP_MAX_IMAGE_SIZE)
setCircleDimmedLayer(true)
}
launcher.launch(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="quickeditor_images"
path="quickeditor/" />
</paths>
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ class AvatarPickerViewModelTest {
fun `given local image when selected then launch cropper action sent`() = runTest {
val file = mockk<File>()
val uri = mockk<Uri>()
every { fileUtils.createTempFile() } returns file
every { fileUtils.createCroppedAvatarFile() } returns file

viewModel = initViewModel()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.gravatar.GravatarApiService
import com.gravatar.GravatarConstants.GRAVATAR_API_BASE_URL_V1
import com.gravatar.GravatarConstants.GRAVATAR_API_BASE_URL_V3
import com.gravatar.services.AuthenticationInterceptor
import com.gravatar.services.AvatarUploadTimeoutInterceptor
import com.gravatar.services.GravatarApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -65,12 +66,14 @@ internal class GravatarSdkContainer private constructor() {

fun getGravatarV3Service(okHttpClient: OkHttpClient? = null, oauthToken: String? = null): GravatarApi {
return getRetrofitApiV3Builder().apply {
client(
(okHttpClient ?: OkHttpClient()).newBuilder().addInterceptor(
AuthenticationInterceptor(oauthToken),
).build(),
)
client(okHttpClient ?: buildOkHttpClient(oauthToken))
}.addConverterFactory(GsonConverterFactory.create(gson))
.build().create(GravatarApi::class.java)
}

private fun buildOkHttpClient(oauthToken: String?) = OkHttpClient()
.newBuilder()
.addInterceptor(AuthenticationInterceptor(oauthToken))
.addInterceptor(AvatarUploadTimeoutInterceptor())
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.gravatar.services

import okhttp3.Interceptor
import okhttp3.Response
import java.time.Duration
import java.util.concurrent.TimeUnit

private const val TIMEOUT_DURATION = 5L

/**
* Increases the timeout for the avatar upload request
*/
internal class AvatarUploadTimeoutInterceptor(
private val timeout: Duration = Duration.ofMinutes(TIMEOUT_DURATION),
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newChain = if (request.method == "POST" && request.url.encodedPath == "/v3/me/avatars") {
chain.withConnectTimeout(timeout.toMillis().toInt(), TimeUnit.MILLISECONDS)
.withReadTimeout(timeout.toMillis().toInt(), TimeUnit.MILLISECONDS)
.withWriteTimeout(timeout.toMillis().toInt(), TimeUnit.MILLISECONDS)
} else {
chain
}
return newChain.proceed(chain.request().newBuilder().build())
}
}