diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 534720cd4f4..d2132d3e3dc 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -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 ----- diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 0fb8a763f22..5554fb0ce3f 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -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") diff --git a/WooCommerce/src/main/AndroidManifest.xml b/WooCommerce/src/main/AndroidManifest.xml index e94638d98d8..e507660bf62 100644 --- a/WooCommerce/src/main/AndroidManifest.xml +++ b/WooCommerce/src/main/AndroidManifest.xml @@ -17,6 +17,10 @@ + + + + + + + + + + diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooCommerceGlideModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooCommerceGlideModule.kt index c890b021eae..ebed908cc9c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooCommerceGlideModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/WooCommerceGlideModule.kt @@ -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)) } /** diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/LiveDataExt.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/LiveDataExt.kt index 516ffa894de..7f821e5e82a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/LiveDataExt.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/LiveDataExt.kt @@ -60,3 +60,13 @@ fun LiveData.filterNotNull(): LiveData { } return mediator } + +fun LiveData.filter(predicate: (T) -> Boolean): LiveData { + val mediator = MediatorLiveData() + mediator.addSource(this) { + if (it != null && predicate(it)) { + mediator.value = it + } + } + return mediator +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerHelper.kt index 1d051ca271b..bb8f72a71aa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerHelper.kt @@ -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 @@ -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) { + 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, source: String) + fun onWPMediaSelected(images: List) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerLoaderFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerLoaderFactory.kt index 38f08c5f089..801fe562396 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerLoaderFactory.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerLoaderFactory.kt @@ -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() } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerSetupFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerSetupFactory.kt index 8cabe090407..7874a4fe290 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerSetupFactory.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerSetupFactory.kt @@ -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 @@ -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") } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerUtil.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerUtil.kt index 4100f457e73..105104befc5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerUtil.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/mediapicker/MediaPickerUtil.kt @@ -3,6 +3,8 @@ 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 @@ -10,6 +12,9 @@ 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 @@ -62,3 +67,21 @@ object MediaPickerUtil { ?: emptyList() } } + +fun Set.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" + ) + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Product.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Product.kt index 698344e8d36..a542f92cb40 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Product.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/Product.kt @@ -94,9 +94,9 @@ data class Product( @Parcelize data class Image( val id: Long, - val name: String, + val name: String?, val source: String, - val dateCreated: Date + val dateCreated: Date? ) : Parcelable fun isSameProduct(product: Product): Boolean { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt index 4ebb5eba3f0..317c8f6bfe1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt @@ -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() } ?: "" } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt index 38376ebdf47..2de0c4c4701 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt @@ -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 @@ -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 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ImageViewerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ImageViewerFragment.kt index b88812cf34c..fe6ea202f54 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ImageViewerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ImageViewerFragment.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentImageViewerBinding @@ -95,7 +96,7 @@ class ImageViewerFragment : Fragment(R.layout.fragment_image_viewer), RequestLis override fun onLoadFailed( e: GlideException?, model: Any?, - target: com.bumptech.glide.request.target.Target?, + target: Target, isFirstResource: Boolean ): Boolean { showProgress(false) @@ -107,10 +108,10 @@ class ImageViewerFragment : Fragment(R.layout.fragment_image_viewer), RequestLis * Glide has loaded the image, hide the progress bar */ override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: com.bumptech.glide.request.target.Target?, - dataSource: DataSource?, + resource: Drawable, + model: Any, + target: Target?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { showProgress(false) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductImagesFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductImagesFragment.kt index 8ba4b6ee96c..8f6f8b7a9ca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductImagesFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductImagesFragment.kt @@ -8,8 +8,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.text.HtmlCompat import androidx.core.view.MenuProvider @@ -26,8 +24,8 @@ import com.woocommerce.android.databinding.FragmentProductImagesBinding import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.extensions.takeIfNotEqualTo -import com.woocommerce.android.mediapicker.MediaPickerUtil.processDeviceMediaResult -import com.woocommerce.android.mediapicker.MediaPickerUtil.processMediaLibraryResult +import com.woocommerce.android.mediapicker.MediaPickerHelper +import com.woocommerce.android.mediapicker.MediaPickerHelper.MediaPickerResultHandler import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.products.ProductImagesViewModel.ProductImagesState import com.woocommerce.android.ui.products.ProductImagesViewModel.ShowCamera @@ -48,17 +46,17 @@ import com.woocommerce.android.viewmodel.fixedHiltNavGraphViewModels import com.woocommerce.android.widgets.WCProductImageGalleryView.OnGalleryImageInteractionListener import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.mediapicker.MediaPickerUtils -import org.wordpress.android.mediapicker.api.MediaPickerSetup 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.WP_MEDIA_LIBRARY -import org.wordpress.android.mediapicker.model.MediaTypes -import org.wordpress.android.mediapicker.ui.MediaPickerActivity import javax.inject.Inject @AndroidEntryPoint class ProductImagesFragment : - BaseProductEditorFragment(R.layout.fragment_product_images), OnGalleryImageInteractionListener, MenuProvider { + BaseProductEditorFragment(R.layout.fragment_product_images), + OnGalleryImageInteractionListener, + MenuProvider, + MediaPickerResultHandler { private val navArgs: ProductImagesFragmentArgs by navArgs() private val viewModel: ProductImagesViewModel by fixedHiltNavGraphViewModels(R.id.nav_graph_image_gallery) @@ -69,7 +67,7 @@ class ProductImagesFragment : lateinit var mediaPickerUtils: MediaPickerUtils @Inject - lateinit var mediaPickerSetupFactory: MediaPickerSetup.Factory + lateinit var mediaPickerHelper: MediaPickerHelper private var _binding: FragmentProductImagesBinding? = null private val binding get() = _binding!! @@ -197,9 +195,9 @@ class ProductImagesFragment : is ShowDialog -> event.showDialog() ShowImageSourceDialog -> showImageSourceDialog() is ShowImageDetail -> showImageDetail(event.image, event.isOpenedDirectly) - ShowStorageChooser -> showLocalDeviceMediaPicker() - ShowCamera -> captureProductImage() - ShowWPMediaPicker -> showMediaLibraryPicker() + ShowStorageChooser -> mediaPickerHelper.showMediaPicker(DEVICE, allowMultiSelect = true) + ShowCamera -> mediaPickerHelper.showMediaPicker(CAMERA) + ShowWPMediaPicker -> mediaPickerHelper.showMediaPicker(WP_MEDIA_LIBRARY, allowMultiSelect = true) is ShowDeleteImageConfirmation -> showConfirmationDialog(event.image) else -> event.isHandled = false } @@ -285,78 +283,25 @@ class ProductImagesFragment : .show() } - private fun showMediaLibraryPicker() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build( - source = WP_MEDIA_LIBRARY, - mediaTypes = MediaTypes.IMAGES, - isMultiSelectAllowed = viewModel.isMultiSelectionAllowed - ) - ) - - mediaLibraryLauncher.launch(intent) - } - - private fun showLocalDeviceMediaPicker() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build( - source = DEVICE, - mediaTypes = MediaTypes.IMAGES, - isMultiSelectAllowed = viewModel.isMultiSelectionAllowed - ) - ) - - mediaDeviceMediaPickerLauncher.launch(intent) - } - - private fun captureProductImage() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build(CAMERA) - ) - - cameraLauncher.launch(intent) - } - - private val mediaDeviceMediaPickerLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { - handleDeviceMediaResult(it, AnalyticsTracker.IMAGE_SOURCE_DEVICE) - } - - private val cameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - handleDeviceMediaResult(it, AnalyticsTracker.IMAGE_SOURCE_CAMERA) - } - - private val mediaLibraryLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - handleMediaLibraryPickerResult(it) - } - - private fun handleDeviceMediaResult(result: ActivityResult, source: String) { - result.processDeviceMediaResult()?.let { mediaUris -> - if (mediaUris.isNotEmpty()) { - AnalyticsTracker.track( - AnalyticsEvent.PRODUCT_IMAGE_ADDED, - mapOf(AnalyticsTracker.KEY_IMAGE_SOURCE to source) - ) - viewModel.uploadProductImages(navArgs.remoteId, mediaUris) - } - } + override fun onExit() { + viewModel.onNavigateBackButtonClicked() } - private fun handleMediaLibraryPickerResult(result: ActivityResult) { - result.processMediaLibraryResult()?.let { + override fun onDeviceMediaSelected(imageUris: List, source: String) { + if (imageUris.isNotEmpty()) { AnalyticsTracker.track( AnalyticsEvent.PRODUCT_IMAGE_ADDED, - mapOf(AnalyticsTracker.KEY_IMAGE_SOURCE to AnalyticsTracker.IMAGE_SOURCE_WPMEDIA) + mapOf(AnalyticsTracker.KEY_IMAGE_SOURCE to source) ) - viewModel.onMediaLibraryImagesAdded(it) + viewModel.uploadProductImages(navArgs.remoteId, imageUris) } } - override fun onExit() { - viewModel.onNavigateBackButtonClicked() + override fun onWPMediaSelected(images: List) { + AnalyticsTracker.track( + AnalyticsEvent.PRODUCT_IMAGE_ADDED, + mapOf(AnalyticsTracker.KEY_IMAGE_SOURCE to AnalyticsTracker.IMAGE_SOURCE_WPMEDIA) + ) + viewModel.onMediaLibraryImagesAdded(images) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt index f91cad59892..e081f0a0a1f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.products.ai +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,6 +16,7 @@ import com.woocommerce.android.extensions.handleDialogResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.mediapicker.MediaPickerHelper import com.woocommerce.android.mediapicker.MediaPickerHelper.MediaPickerResultHandler +import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @@ -80,13 +82,6 @@ class AddProductWithAIFragment : BaseFragment(), MediaPickerResultHandler { } } - override fun onMediaSelected(mediaUri: String) { - findNavController().navigateSafely( - directions = AddProductWithAIFragmentDirections - .actionAddProductWithAIFragmentToPackagePhotoBottomSheetFragment(mediaUri) - ) - } - private fun handleResults() { handleDialogResult( key = AIProductNameBottomSheetFragment.KEY_AI_GENERATED_PRODUCT_NAME_RESULT, @@ -109,4 +104,23 @@ class AddProductWithAIFragment : BaseFragment(), MediaPickerResultHandler { .actionAddProductWithAIFragmentToAIProductNameBottomSheetFragment(initialName) findNavController().navigateSafely(action) } + + override fun onDeviceMediaSelected(imageUris: List, source: String) { + if (imageUris.isNotEmpty()) { + onImageSelected(imageUris.first().toString()) + } + } + + override fun onWPMediaSelected(images: List) { + if (images.isNotEmpty()) { + onImageSelected(images.first().source) + } + } + + private fun onImageSelected(mediaUri: String) { + findNavController().navigateSafely( + directions = AddProductWithAIFragmentDirections + .actionAddProductWithAIFragmentToPackagePhotoBottomSheetFragment(mediaUri) + ) + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/PackagePhotoBottomSheetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/PackagePhotoBottomSheetFragment.kt index fd812bc15e5..887313961b4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/PackagePhotoBottomSheetFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/PackagePhotoBottomSheetFragment.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.products.ai +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,6 +11,7 @@ import androidx.fragment.app.viewModels import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.mediapicker.MediaPickerHelper import com.woocommerce.android.mediapicker.MediaPickerHelper.MediaPickerResultHandler +import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.compose.theme.WooTheme import com.woocommerce.android.ui.products.ai.PackagePhotoViewModel.ShowMediaLibrary import com.woocommerce.android.ui.products.ai.PackagePhotoViewModel.ShowMediaLibraryDialog @@ -40,10 +42,6 @@ class PackagePhotoBottomSheetFragment : WCBottomSheetDialogFragment(), MediaPick } } - override fun onMediaSelected(mediaUri: String) { - viewModel.onImageChanged(mediaUri) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -64,4 +62,19 @@ class PackagePhotoBottomSheetFragment : WCBottomSheetDialogFragment(), MediaPick } } } + override fun onDeviceMediaSelected(imageUris: List, source: String) { + if (imageUris.isNotEmpty()) { + onImageSelected(imageUris.first().toString()) + } + } + + override fun onWPMediaSelected(images: List) { + if (images.isNotEmpty()) { + onImageSelected(images.first().source) + } + } + + private fun onImageSelected(mediaUri: String) { + viewModel.onImageChanged(mediaUri) + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/downloads/AddProductDownloadBottomSheetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/downloads/AddProductDownloadBottomSheetFragment.kt index 0d500a372d2..41ae9f91e5b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/downloads/AddProductDownloadBottomSheetFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/downloads/AddProductDownloadBottomSheetFragment.kt @@ -5,14 +5,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.R import com.woocommerce.android.databinding.DialogProductAddDownloadableFileBinding -import com.woocommerce.android.mediapicker.MediaPickerUtil.processDeviceMediaResult -import com.woocommerce.android.mediapicker.MediaPickerUtil.processMediaLibraryResult +import com.woocommerce.android.mediapicker.MediaPickerHelper +import com.woocommerce.android.mediapicker.MediaPickerHelper.MediaPickerResultHandler +import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.base.UIMessageResolver import com.woocommerce.android.ui.products.ProductDetailViewModel import com.woocommerce.android.ui.products.ProductNavigationTarget.ViewProductDownloadDetails @@ -24,19 +23,21 @@ import com.woocommerce.android.ui.products.downloads.AddProductDownloadViewModel import com.woocommerce.android.viewmodel.fixedHiltNavGraphViewModels import com.woocommerce.android.widgets.WCBottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint -import org.wordpress.android.mediapicker.api.MediaPickerSetup -import org.wordpress.android.mediapicker.model.MediaTypes -import org.wordpress.android.mediapicker.ui.MediaPickerActivity +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.DOCUMENTS +import org.wordpress.android.mediapicker.model.MediaTypes.EVERYTHING +import org.wordpress.android.mediapicker.model.MediaTypes.MEDIA import javax.inject.Inject @AndroidEntryPoint -class AddProductDownloadBottomSheetFragment : WCBottomSheetDialogFragment() { +class AddProductDownloadBottomSheetFragment : WCBottomSheetDialogFragment(), MediaPickerResultHandler { @Inject lateinit var navigator: ProductNavigator @Inject lateinit var uiMessageResolver: UIMessageResolver @Inject - lateinit var mediaPickerSetupFactory: MediaPickerSetup.Factory + lateinit var mediaPickerHelper: MediaPickerHelper private val viewModel: AddProductDownloadViewModel by viewModels() private val parentViewModel: ProductDetailViewModel by fixedHiltNavGraphViewModels(R.id.nav_graph_products) @@ -66,9 +67,18 @@ class AddProductDownloadBottomSheetFragment : WCBottomSheetDialogFragment() { private fun setupObservers(viewModel: AddProductDownloadViewModel) { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { - is PickFileFromMedialLibrary -> showMediaLibraryPicker() - is PickMediaFileFromDevice -> showLocalDeviceMediaPicker() - is PickDocumentFromDevice -> showLocalDeviceDocumentPicker() + is PickFileFromMedialLibrary -> mediaPickerHelper.showMediaPicker( + source = WP_MEDIA_LIBRARY, + mediaTypes = EVERYTHING + ) + is PickMediaFileFromDevice -> mediaPickerHelper.showMediaPicker( + source = SYSTEM_PICKER, + mediaTypes = MEDIA + ) + is PickDocumentFromDevice -> mediaPickerHelper.showMediaPicker( + source = SYSTEM_PICKER, + mediaTypes = DOCUMENTS + ) is AddFile -> { findNavController().navigateUp() parentViewModel.handleSelectedDownloadableFile(event.uri.toString()) @@ -80,65 +90,15 @@ class AddProductDownloadBottomSheetFragment : WCBottomSheetDialogFragment() { } } - private val mediaLibraryLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - handleMediaLibraryPickerResult(it) - } - - private val mediaDeviceMediaPickerLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { - handleDeviceMediaResult(it) - } - - private fun handleMediaLibraryPickerResult(result: ActivityResult) { - result.processMediaLibraryResult()?.let { images -> - images.forEach { - viewModel.launchFileUpload(Uri.parse(it.source)) - } + override fun onDeviceMediaSelected(imageUris: List, source: String) { + if (imageUris.isNotEmpty()) { + viewModel.launchFileUpload(imageUris.first()) } } - private fun showMediaLibraryPicker() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build( - source = MediaPickerSetup.DataSource.WP_MEDIA_LIBRARY, - mediaTypes = MediaTypes.EVERYTHING - ) - ) - - mediaLibraryLauncher.launch(intent) - } - - private fun handleDeviceMediaResult(result: ActivityResult) { - result.processDeviceMediaResult()?.let { mediaUris -> - if (mediaUris.isNotEmpty()) { - viewModel.launchFileUpload(mediaUris.first()) - } + override fun onWPMediaSelected(images: List) { + images.forEach { + viewModel.launchFileUpload(Uri.parse(it.source)) } } - - private fun showLocalDeviceMediaPicker() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build( - source = MediaPickerSetup.DataSource.DEVICE, - mediaTypes = MediaTypes.MEDIA - ) - ) - - mediaDeviceMediaPickerLauncher.launch(intent) - } - - private fun showLocalDeviceDocumentPicker() { - val intent = MediaPickerActivity.buildIntent( - requireContext(), - mediaPickerSetupFactory.build( - source = MediaPickerSetup.DataSource.SYSTEM_PICKER, - mediaTypes = MediaTypes.DOCUMENTS - ) - ) - - mediaDeviceMediaPickerLauncher.launch(intent) - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingFlow.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingFlow.kt index 357a8e1eac6..53d0bd09d42 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingFlow.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingFlow.kt @@ -338,14 +338,15 @@ private fun InstallationLoadingIndicator(showLoadingIndicator: Boolean, modifier val progressColor = colorResource(id = R.color.woo_purple_50) val startAngle by if (showLoadingIndicator) { - val transition = rememberInfiniteTransition() + val transition = rememberInfiniteTransition(label = "") transition.animateFloat( - -90f, - 270f, - infiniteRepeatable( + initialValue = -90f, + targetValue = 270f, + animationSpec = infiniteRepeatable( animation = tween(1332, easing = LinearEasing) - ) + ), + label = "" ) } else { remember { mutableStateOf(-90f) } @@ -375,9 +376,10 @@ private fun InstallationLoadingIndicator(showLoadingIndicator: Boolean, modifier @Preview @Composable +@Suppress("UnusedContentLambdaTargetStateParameter") private fun PreInstallationPreview() { WooThemeWithBackground { - AnimatedContent(targetState = Unit) { + AnimatedContent(targetState = Unit, label = "") { InstallWCShippingFlow( viewState = PreInstallation( extensionsName = R.string.install_wc_shipping_extension_name, @@ -394,9 +396,10 @@ private fun PreInstallationPreview() { @Preview @Composable +@Suppress("UnusedContentLambdaTargetStateParameter") private fun InstallationOngoingPreview() { WooThemeWithBackground { - AnimatedContent(targetState = Unit) { + AnimatedContent(targetState = Unit, label = "") { InstallationContent( viewState = InstallationOngoing( extensionsName = R.string.install_wc_shipping_extension_name, diff --git a/build.gradle b/build.gradle index 09a5f6b7745..bb1a926df40 100644 --- a/build.gradle +++ b/build.gradle @@ -112,7 +112,7 @@ ext { materialVersion = '1.6.1' hiltJetpackVersion = '1.0.0' wordPressUtilsVersion = '3.5.0' - mediapickerVersion = '0.2' + mediapickerVersion = 'trunk-10f3b7d3aeb31bb0bacacde3d9fc2456c09105a9' wordPressLoginVersion = '1.8.0' aboutAutomatticVersion = '0.0.6' automatticTracksVersion = '3.2.0' diff --git a/settings.gradle b/settings.gradle index f3e03470a5a..06f5c39bc50 100644 --- a/settings.gradle +++ b/settings.gradle @@ -57,7 +57,7 @@ gradle.ext.fluxCWooCommercePluginBinaryPath = "org.wordpress.fluxc.plugins:wooco gradle.ext.loginFlowBinaryPath = "org.wordpress:login" gradle.ext.mediaPickerBinaryPath = "org.wordpress:mediapicker" gradle.ext.mediaPickerDomainBinaryPath = "org.wordpress.mediapicker:domain" -gradle.ext.mediaPickerSourceDeviceBinaryPath = "org.wordpress.mediapicker:source-device" +gradle.ext.mediaPickerSourceCameraBinaryPath = "org.wordpress.mediapicker:source-camera" gradle.ext.mediaPickerSourceGifBinaryPath = "org.wordpress.mediapicker:source-gif" gradle.ext.mediaPickerSourceWordPressBinaryPath = "org.wordpress.mediapicker:source-wordpress"