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

Photo picker #10141

Merged
merged 31 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0bae491
Update necessary dependencies
0nko Nov 9, 2023
a431709
Replace media picker device source with camera source module
0nko Nov 9, 2023
edd9f45
Replace the deprecated play core library with new granular dependencies
0nko Nov 9, 2023
e803abb
Add the photo picker backport configuration to the manifest
0nko Nov 9, 2023
8b20a01
Add the missing LiveData extension function
0nko Nov 9, 2023
9c93cde
Fix glide setup after library update
0nko Nov 9, 2023
aebb58a
Replace the media picker device source with photo picker
0nko Nov 9, 2023
e00f619
Update the product model properties' nullability
0nko Nov 9, 2023
cc24608
Update the Compose animation functions after library update
0nko Nov 9, 2023
755df2d
Update the glide callbacks after library update
0nko Nov 9, 2023
7fb0f93
Fix ktlint issues
0nko Nov 9, 2023
a405cb4
Refactor the media picker helper to be more reusable
0nko Nov 10, 2023
56227fb
Reuse the media picker helper in all places where media picker is used
0nko Nov 10, 2023
278be69
Remove restricted API usage
0nko Nov 10, 2023
4a9d472
Optimize imports
0nko Nov 10, 2023
42e54ac
Fix lint issues
0nko Nov 10, 2023
6f6f26a
Fix ktlint issues
0nko Nov 10, 2023
aab09a4
Update the MediaPicker hash
0nko Nov 10, 2023
bca9469
Revert the changes related to the animation deprecations
0nko Nov 13, 2023
6647482
Update the MediaPicker reference
0nko Nov 13, 2023
eaedcb1
Revert "Replace the deprecated play core library with new granular de…
0nko Nov 13, 2023
8273e47
Update the PlayCore library version
0nko Nov 13, 2023
f662c97
Merge branch 'release/16.2' into feature/android-photo-picker
0nko Nov 13, 2023
d5703fd
Update the release notes
0nko Nov 13, 2023
40236e0
Bump minor Kotlin version
0nko Nov 13, 2023
108681a
Update dependencies
0nko Nov 14, 2023
7dc371d
Merge branch 'release/16.2' into feature/android-photo-picker
0nko Nov 14, 2023
cd89efa
Remove imported READ_EXTERNAL_STORAGE permission from the merged mani…
0nko Nov 14, 2023
b06aa3f
Add system media picker helper functions
0nko Nov 14, 2023
e080b19
Fix the product downloads media picker
0nko Nov 14, 2023
878bb05
Optimize imports
0nko Nov 14, 2023
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
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
-----
- [*] [Internal] Improve handling of Jetpack Connection for Jetpack CP sites when using Application Passwords [https://github.com/woocommerce/woocommerce-android/pull/10083]
- [***] Custom Amounts M2: Redesign Payments and Customers section [https://github.com/woocommerce/woocommerce-android/pull/10122]
- [**] Replaced the custom device image picker with Android photo picker, which doesn't require special image & video permissions [https://github.com/woocommerce/woocommerce-android/pull/10141]

16.1
-----
Expand Down
11 changes: 3 additions & 8 deletions WooCommerce/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,9 @@ dependencies {

implementation "com.tinder.statemachine:statemachine:$stateMachineVersion"

implementation("${gradle.ext.mediaPickerBinaryPath}:$mediapickerVersion") {
exclude group: "org.wordpress", module: "utils"
}
implementation("${gradle.ext.mediaPickerSourceDeviceBinaryPath}:$mediapickerVersion")
implementation("${gradle.ext.mediaPickerSourceWordPressBinaryPath}:$mediapickerVersion") {
exclude group: "org.wordpress", module: "utils"
exclude group: "org.wordpress", module: "fluxc"
}
implementation("${gradle.ext.mediaPickerBinaryPath}:$mediapickerVersion")
implementation("${gradle.ext.mediaPickerSourceCameraBinaryPath}:$mediapickerVersion")
implementation("${gradle.ext.mediaPickerSourceWordPressBinaryPath}:$mediapickerVersion")

// Jetpack Compose
implementation platform("androidx.compose:compose-bom:$composeBOMVersion")
Expand Down
16 changes: 16 additions & 0 deletions WooCommerce/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this, I think we need to move this to be specific to release builds (in a separate PR), otherwise it might break automated screenshots that rely on the permission

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"
tools:node="remove" />

<application
android:name=".WooCommerce"
Expand Down Expand Up @@ -228,6 +232,18 @@
android:value="barcode_ui,ocr" />

<meta-data android:name="io.sentry.traces.activity.enable" android:value="false" />

<!-- Trigger Google Play services to install the backported photo picker module. -->
<service android:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
</intent-filter>
<meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class WooCommerceGlideModule : AppGlideModule() {
WooCommerceGlideEntryPoint::class.java
).requestQueue()

glide.registry.replace(GlideUrl::class.java, InputStream::class.java, VolleyUrlLoader.Factory(requestQueue))
registry.replace(GlideUrl::class.java, InputStream::class.java, VolleyUrlLoader.Factory(requestQueue))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ fun <T : Any> LiveData<T?>.filterNotNull(): LiveData<T> {
}
return mediator
}

fun <T> LiveData<T>.filter(predicate: (T) -> Boolean): LiveData<T> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was originally a part of the MediaPicker library and was removed.

val mediator = MediatorLiveData<T>()
mediator.addSource(this) {
if (it != null && predicate(it)) {
mediator.value = it
}
}
return mediator
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package com.woocommerce.android.mediapicker

import android.net.Uri
import androidx.activity.result.ActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts.PickMultipleVisualMedia
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.fragment.app.Fragment
import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.mediapicker.MediaPickerUtil.processDeviceMediaResult
import com.woocommerce.android.mediapicker.MediaPickerUtil.processMediaLibraryResult
import com.woocommerce.android.model.Product
import dagger.hilt.android.scopes.FragmentScoped
import org.wordpress.android.mediapicker.api.MediaPickerSetup
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.CAMERA
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.DEVICE
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.SYSTEM_PICKER
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY
import org.wordpress.android.mediapicker.model.MediaTypes
import org.wordpress.android.mediapicker.model.MediaTypes.IMAGES
import org.wordpress.android.mediapicker.ui.MediaPickerActivity
import javax.inject.Inject

Expand All @@ -17,44 +28,106 @@ class MediaPickerHelper @Inject constructor(
private val fragment: Fragment,
private val mediaPickerSetupFactory: MediaPickerSetup.Factory
) {
private val deviceLibraryLauncher = fragment.registerForActivityResult(StartActivityForResult()) {
handleDeviceMediaResult(it)

private val photoPicker = fragment.registerForActivityResult(PickVisualMedia()) { uri ->
handlePhotoPickerResult(uri?.let { listOf(it) } ?: emptyList())
}

private val multiPhotoPicker = fragment.registerForActivityResult(PickMultipleVisualMedia()) { uris ->
handlePhotoPickerResult(uris)
}

private val mediaLibraryLauncher = fragment.registerForActivityResult(StartActivityForResult()) {
handleMediaLibraryPickerResult(it)
}

fun showMediaPicker(source: DataSource) {
private val cameraLauncher = fragment.registerForActivityResult(StartActivityForResult()) {
handleMediaPickerResult(it, AnalyticsTracker.IMAGE_SOURCE_CAMERA)
}

private val systemMediaPickerLauncher = fragment.registerForActivityResult(StartActivityForResult()) {
handleMediaPickerResult(it, AnalyticsTracker.IMAGE_SOURCE_DEVICE)
}

fun showMediaPicker(source: DataSource, allowMultiSelect: Boolean = false, mediaTypes: MediaTypes = IMAGES) {
when (source) {
WP_MEDIA_LIBRARY -> launchWPMediaLibrary(source, allowMultiSelect, mediaTypes)
CAMERA -> launchCamera()
DEVICE -> launchPhotoPicker(allowMultiSelect, mediaTypes)
SYSTEM_PICKER -> launchSystemMediaPicker(mediaTypes)
else -> throw IllegalArgumentException("Unsupported data source: $source")
}
}

private fun launchSystemMediaPicker(mediaTypes: MediaTypes) {
val intent = MediaPickerActivity.buildIntent(
fragment.requireContext(),
mediaPickerSetupFactory.build(
source = SYSTEM_PICKER,
mediaTypes = mediaTypes
)
)

systemMediaPickerLauncher.launch(intent)
}

private fun launchPhotoPicker(allowMultiSelect: Boolean, mediaTypes: MediaTypes) {
if (allowMultiSelect) {
multiPhotoPicker.launch(
PickVisualMediaRequest(mediaTypes.allowedTypes.toPhotoPickerTypes())
)
} else {
photoPicker.launch(
PickVisualMediaRequest(mediaTypes.allowedTypes.toPhotoPickerTypes())
)
}
}

private fun launchCamera() {
val intent = MediaPickerActivity.buildIntent(
fragment.requireContext(),
mediaPickerSetupFactory.build(CAMERA)
)

cameraLauncher.launch(intent)
}

private fun launchWPMediaLibrary(
source: DataSource,
allowMultiSelect: Boolean,
mediaTypes: MediaTypes
) {
val mediaPickerIntent = MediaPickerActivity.buildIntent(
context = fragment.requireContext(),
mediaPickerSetupFactory.build(
source = source,
mediaTypes = MediaTypes.IMAGES
mediaTypes = mediaTypes,
isMultiSelectAllowed = allowMultiSelect
)
)
if (source == DataSource.WP_MEDIA_LIBRARY) {
mediaLibraryLauncher.launch(mediaPickerIntent)
} else {
deviceLibraryLauncher.launch(mediaPickerIntent)
}
mediaLibraryLauncher.launch(mediaPickerIntent)
}

private fun handleDeviceMediaResult(result: ActivityResult) {
result.processDeviceMediaResult()?.let { mediaUris ->
if (mediaUris.isNotEmpty()) {
(fragment as MediaPickerResultHandler).onMediaSelected(mediaUris.first().toString())
}
private fun handlePhotoPickerResult(uris: List<Uri>) {
if (uris.isNotEmpty()) {
(fragment as MediaPickerResultHandler).onDeviceMediaSelected(uris, AnalyticsTracker.IMAGE_SOURCE_DEVICE)
}
}

private fun handleMediaLibraryPickerResult(result: ActivityResult) {
result.processMediaLibraryResult()?.let { mediaItems ->
(fragment as MediaPickerResultHandler).onMediaSelected(mediaItems.map { it.source }.first())
(fragment as MediaPickerResultHandler).onWPMediaSelected(mediaItems)
}
}

private fun handleMediaPickerResult(result: ActivityResult, source: String) {
result.processDeviceMediaResult()?.let { uris ->
(fragment as MediaPickerResultHandler).onDeviceMediaSelected(uris, source)
}
}

interface MediaPickerResultHandler {
fun onMediaSelected(mediaUri: String)
fun onDeviceMediaSelected(imageUris: List<Uri>, source: String)
fun onWPMediaSelected(images: List<Product.Image>)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ package com.woocommerce.android.mediapicker
import org.wordpress.android.mediapicker.api.MediaPickerSetup
import org.wordpress.android.mediapicker.loader.MediaLoader
import org.wordpress.android.mediapicker.loader.MediaLoaderFactory
import org.wordpress.android.mediapicker.source.device.DeviceMediaSource
import org.wordpress.android.mediapicker.source.wordpress.MediaLibrarySource
import javax.inject.Inject

// A factory class responsible for building an image-loader class, which is specific to a source.
class MediaPickerLoaderFactory @Inject constructor(
private val deviceMediaSourceFactory: DeviceMediaSource.Factory,
private val mediaLibrarySourceFactory: MediaLibrarySource.Factory
) : MediaLoaderFactory {
override fun build(mediaPickerSetup: MediaPickerSetup): MediaLoader {
return when (mediaPickerSetup.primaryDataSource) {
MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY -> {
mediaLibrarySourceFactory.build(mediaPickerSetup.allowedTypes)
}
else -> deviceMediaSourceFactory.build(mediaPickerSetup.allowedTypes)
}.toMediaLoader()
return mediaLibrarySourceFactory.build(mediaPickerSetup.allowedTypes).toMediaLoader()
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.woocommerce.android.mediapicker

import com.woocommerce.android.R
import org.wordpress.android.mediapicker.api.MediaPickerSetup
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.CAMERA
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.DEVICE
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.SYSTEM_PICKER
import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY
import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.VISIBLE_UNTOGGLED
import org.wordpress.android.mediapicker.model.MediaTypes
import org.wordpress.android.mediapicker.source.device.DeviceMediaPickerSetup
import org.wordpress.android.mediapicker.setup.SystemMediaPickerSetup
import org.wordpress.android.mediapicker.source.camera.CameraMediaPickerSetup
import org.wordpress.android.mediapicker.source.wordpress.MediaLibraryPickerSetup
import java.security.InvalidParameterException
import javax.inject.Inject
Expand All @@ -21,26 +19,12 @@ class MediaPickerSetupFactory @Inject constructor() : MediaPickerSetup.Factory {
isMultiSelectAllowed: Boolean
): MediaPickerSetup {
return when (source) {
CAMERA -> DeviceMediaPickerSetup.buildCameraPicker()
CAMERA -> CameraMediaPickerSetup.build()
WP_MEDIA_LIBRARY -> MediaLibraryPickerSetup.build(
mediaTypes = mediaTypes,
canMultiSelect = isMultiSelectAllowed
)

DEVICE -> MediaPickerSetup(
primaryDataSource = DEVICE,
isMultiSelectEnabled = isMultiSelectAllowed,
areResultsQueued = false,
searchMode = VISIBLE_UNTOGGLED,
availableDataSources = setOf(SYSTEM_PICKER),
allowedTypes = mediaTypes.allowedTypes,
title = R.string.photo_picker_title
)

SYSTEM_PICKER -> DeviceMediaPickerSetup.buildSystemPicker(
mediaTypes = mediaTypes,
canMultiSelect = false
)
SYSTEM_PICKER -> SystemMediaPickerSetup.build(mediaTypes = mediaTypes, canMultiSelect = false)

else -> throw InvalidParameterException("${source.name} source is not supported")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package com.woocommerce.android.mediapicker
import android.net.Uri
import android.os.Bundle
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType
import androidx.appcompat.app.AppCompatActivity
import com.woocommerce.android.extensions.parcelableArrayList
import com.woocommerce.android.model.Product
import com.woocommerce.android.util.WooLog
import org.wordpress.android.mediapicker.MediaPickerConstants
import org.wordpress.android.mediapicker.api.MediaPickerSetup
import org.wordpress.android.mediapicker.model.MediaItem
import org.wordpress.android.mediapicker.model.MediaType
import org.wordpress.android.mediapicker.model.MediaType.IMAGE
import org.wordpress.android.mediapicker.model.MediaType.VIDEO
import org.wordpress.android.util.DateTimeUtils
import java.security.InvalidParameterException

Expand Down Expand Up @@ -62,3 +67,21 @@ object MediaPickerUtil {
?: emptyList()
}
}

fun Set<MediaType>.toPhotoPickerTypes(): VisualMediaType {
return when {
contains(IMAGE) && contains(VIDEO) -> {
PickVisualMedia.ImageAndVideo
}
contains(IMAGE) -> {
PickVisualMedia.ImageOnly
}
contains(VIDEO) -> {
PickVisualMedia.VideoOnly
} else -> {
throw IllegalArgumentException(
"Missing or unsupported photo picker media types: $this"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ data class Product(
@Parcelize
data class Image(
val id: Long,
val name: String,
val name: String?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the cause of this change and the similar one below @0nko?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An update in the FluxC library: wordpress-mobile/WordPress-FluxC-Android#2886

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @0nko for the clariffication, I was confused because we updated the repo to align with this FluxC change a while ago #10078, but now I understand, this is caused by this commit wordpress-mobile/WordPress-MediaPicker-Android@9629167

val source: String,
val dateCreated: Date
val dateCreated: Date?
) : Parcelable

fun isSameProduct(product: Product): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ open class ProductVariation(
json.addProperty("id", variantImage.id)
json.addProperty("name", variantImage.name)
json.addProperty("src", variantImage.source)
json.addProperty("date_created_gmt", variantImage.dateCreated.formatToYYYYmmDDhhmmss())
json.addProperty(
/* property = */ "date_created_gmt",
/* value = */ variantImage.dateCreated?.formatToYYYYmmDDhhmmss() ?: ""
)
}.toString()
} ?: ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_IPP_BANN
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_IPP_BANNER_SOURCE_ORDER_LIST
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.extensions.NotificationReceivedEvent
import com.woocommerce.android.extensions.filter
import com.woocommerce.android.extensions.filterNotNull
import com.woocommerce.android.model.FeatureFeedbackSettings
import com.woocommerce.android.model.RequestResult.SUCCESS
Expand Down Expand Up @@ -78,7 +79,6 @@ import org.wordpress.android.fluxc.store.ListStore
import org.wordpress.android.fluxc.store.WCOrderStore
import org.wordpress.android.fluxc.store.WCOrderStore.OnOrderChanged
import org.wordpress.android.fluxc.store.WCOrderStore.OnOrderSummariesFetched
import org.wordpress.android.mediapicker.util.filter
import javax.inject.Inject

private const val EMPTY_VIEW_THROTTLE = 250L
Expand Down
Loading
Loading