diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index d6fcc52873c0a..5ac160a6e9b5a 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -26,6 +26,7 @@ export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, + MEDIA_TYPE_AUDIO, MEDIA_TYPE_ANY, } from './media-upload'; export { default as MediaUploadProgress } from './media-upload-progress'; diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index 715f5ec2ceeb4..1193b8a4122d4 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -12,6 +12,7 @@ import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, + MEDIA_TYPE_AUDIO, } from '@wordpress/block-editor'; import { withPreferredColorScheme } from '@wordpress/compose'; import { useRef } from '@wordpress/element'; @@ -65,6 +66,7 @@ function MediaPlaceholder( props ) { const isOneType = allowedTypes.length === 1; const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO ); + const isAudio = isOneType && allowedTypes.includes( MEDIA_TYPE_AUDIO ); let placeholderTitle = labels.title; if ( placeholderTitle === undefined ) { @@ -73,6 +75,8 @@ function MediaPlaceholder( props ) { placeholderTitle = __( 'Image' ); } else if ( isVideo ) { placeholderTitle = __( 'Video' ); + } else if ( isAudio ) { + placeholderTitle = __( 'Audio' ); } } @@ -82,6 +86,8 @@ function MediaPlaceholder( props ) { instructions = __( 'ADD IMAGE' ); } else if ( isVideo ) { instructions = __( 'ADD VIDEO' ); + } else if ( isAudio ) { + instructions = __( 'ADD AUDIO' ); } else { instructions = __( 'ADD IMAGE OR VIDEO' ); } @@ -92,6 +98,8 @@ function MediaPlaceholder( props ) { accessibilityHint = __( 'Double tap to select an image' ); } else if ( isVideo ) { accessibilityHint = __( 'Double tap to select a video' ); + } else if ( isAudio ) { + accessibilityHint = __( 'Double tap to select an audio file' ); } const emptyStateTitleStyle = getStylesFromColorScheme( diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index dd3896cfdcbb8..4d2c4e1244378 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -19,6 +19,7 @@ import { export const MEDIA_TYPE_IMAGE = 'image'; export const MEDIA_TYPE_VIDEO = 'video'; +export const MEDIA_TYPE_AUDIO = 'audio'; export const MEDIA_TYPE_ANY = 'any'; export const OPTION_TAKE_VIDEO = __( 'Take a Video' ); @@ -83,7 +84,12 @@ export class MediaUpload extends Component { id: mediaSources.siteMediaLibrary, value: mediaSources.siteMediaLibrary, label: __( 'WordPress Media Library' ), - types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, MEDIA_TYPE_ANY ], + types: [ + MEDIA_TYPE_IMAGE, + MEDIA_TYPE_VIDEO, + MEDIA_TYPE_AUDIO, + MEDIA_TYPE_ANY, + ], icon: wordpress, mediaLibrary: true, }; @@ -151,6 +157,7 @@ export class MediaUpload extends Component { const isOneType = allowedTypes.length === 1; const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO ); + const isAudio = isOneType && allowedTypes.includes( MEDIA_TYPE_AUDIO ); const isAnyType = isOneType && allowedTypes.includes( MEDIA_TYPE_ANY ); const isImageOrVideo = @@ -179,8 +186,19 @@ export class MediaUpload extends Component { } else { pickerTitle = __( 'Choose image or video' ); } + } else if ( isAudio ) { + if ( isReplacingMedia ) { + pickerTitle = __( 'Replace audio' ); + } else { + pickerTitle = __( 'Choose audio' ); + } } else if ( isAnyType ) { pickerTitle = __( 'Choose file' ); + if ( isReplacingMedia ) { + pickerTitle = __( 'Replace file' ); + } else { + pickerTitle = __( 'Choose file' ); + } } const getMediaOptions = () => ( diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js new file mode 100644 index 0000000000000..3e66485f93699 --- /dev/null +++ b/packages/block-library/src/audio/edit.native.js @@ -0,0 +1,220 @@ +/** + * External dependencies + */ +import { Text, TouchableWithoutFeedback } from 'react-native'; +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { View } from '@wordpress/primitives'; +import { + PanelBody, + SelectControl, + ToggleControl, + withNotices, + ToolbarButton, + ToolbarGroup, +} from '@wordpress/components'; +import { + BlockCaption, + BlockControls, + BlockIcon, + InspectorControls, + MediaPlaceholder, + MediaUpload, + MediaUploadProgress, +} from '@wordpress/block-editor'; +import { __, sprintf } from '@wordpress/i18n'; +import { audio as icon, replace } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; + +const ALLOWED_MEDIA_TYPES = [ 'audio' ]; + +function AudioEdit( { + attributes, + noticeOperations, + setAttributes, + isSelected, + noticeUI, + insertBlocksAfter, + onFocus, + onBlur, + clientId, +} ) { + const { id, autoplay, loop, preload, src } = attributes; + + const [ isCaptionSelected, setIsCaptionSelected ] = useState( false ); + + const onFileChange = ( { mediaId, mediaUrl } ) => { + setAttributes( { id: mediaId, src: mediaUrl } ); + }; + + const onError = () => { + // TODO: Set up error state + onUploadError( __( 'Error' ) ); + }; + + function toggleAttribute( attribute ) { + return ( newValue ) => { + setAttributes( { [ attribute ]: newValue } ); + }; + } + + function onSelectURL() { + // TODO: Set up add audio from URL flow + } + + function onUploadError( message ) { + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + + // const { setAttributes, isSelected, noticeUI } = this.props; + function onSelectAudio( media ) { + if ( ! media || ! media.url ) { + // in this case there was an error and we should continue in the editing state + // previous attributes should be removed because they may be temporary blob urls + setAttributes( { src: undefined, id: undefined } ); + return; + } + // sets the block's attribute and updates the edit component from the + // selected media, then switches off the editing UI + setAttributes( { src: media.url, id: media.id } ); + } + + function onAudioPress() { + setIsCaptionSelected( false ); + } + + function onFocusCaption() { + if ( ! isCaptionSelected ) { + setIsCaptionSelected( true ); + } + } + + if ( ! src ) { + return ( + + } + onSelect={ onSelectAudio } + onSelectURL={ onSelectURL } + accept="audio/*" + allowedTypes={ ALLOWED_MEDIA_TYPES } + value={ attributes } + notices={ noticeUI } + onError={ onUploadError } + onFocus={ onFocus } + /> + + ); + } + + function getBlockControls( open ) { + return ( + + + + + + ); + } + + function getBlockUI( open, getMediaOptions ) { + return ( + { + return ( + + { ! isCaptionSelected && getBlockControls( open ) } + { getMediaOptions() } + + ⏯ Audio Player goes here.{ ' ' } + { isUploadInProgress && 'Uploading...' } + { isUploadFailed && 'ERROR' } + + + ); + } } + /> + ); + } + + return ( + + + + + + + + setAttributes( { + preload: value || undefined, + } ) + } + options={ [ + { value: '', label: __( 'Browser default' ) }, + { value: 'auto', label: __( 'Auto' ) }, + { value: 'metadata', label: __( 'Metadata' ) }, + { value: 'none', label: __( 'None' ) }, + ] } + /> + + + { + return getBlockUI( open, getMediaOptions ); + } } + /> + + isEmpty( caption ) + ? /* translators: accessibility text. Empty Audio caption. */ + __( 'Audio caption. Empty' ) + : sprintf( + /* translators: accessibility text. %s: Audio caption. */ + __( 'Audio caption. %s' ), + caption + ) + } + clientId={ clientId } + isSelected={ isCaptionSelected } + onFocus={ onFocusCaption } + onBlur={ onBlur } + insertBlocksAfter={ insertBlocksAfter } + /> + + + ); +} +export default withNotices( AudioEdit ); diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 65d4e94ed6aca..21e033f477d8c 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -224,6 +224,7 @@ export const registerCoreBlocks = () => { socialLinks, pullquote, file, + devOnly( audio ), devOnly( reusableBlock ), ].forEach( registerBlock ); diff --git a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index 6514acedfb328..fb3b56d79b49d 100644 --- a/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -138,6 +138,7 @@ public interface OnMediaLibraryButtonListener { void onMediaLibraryVideoButtonClicked(boolean allowMultipleSelection); void onMediaLibraryMediaButtonClicked(boolean allowMultipleSelection); void onMediaLibraryFileButtonClicked(boolean allowMultipleSelection); + void onMediaLibraryAudioButtonClicked(boolean allowMultipleSelection); void onUploadPhotoButtonClicked(boolean allowMultipleSelection); void onCapturePhotoButtonClicked(); void onUploadVideoButtonClicked(boolean allowMultipleSelection); @@ -148,6 +149,7 @@ public interface OnMediaLibraryButtonListener { void onCancelUploadForMediaDueToDeletedBlock(int mediaId); ArrayList onGetOtherMediaImageOptions(); ArrayList onGetOtherMediaFileOptions(); + ArrayList onGetOtherMediaAudioFileOptions(); void onOtherMediaButtonClicked(String mediaSource, boolean allowMultipleSelection); } @@ -233,14 +235,24 @@ public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelected mMediaPickedByUserOnBlock = true; mAppendsMultipleSelectedToSiblingBlocks = !allowMultipleSelection; mMediaSelectedCallback = mediaSelectedCallback; - if (mediaType == MediaType.IMAGE) { - mOnMediaLibraryButtonListener.onMediaLibraryImageButtonClicked(allowMultipleSelection); - } else if (mediaType == MediaType.VIDEO) { - mOnMediaLibraryButtonListener.onMediaLibraryVideoButtonClicked(allowMultipleSelection); - } else if (mediaType == MediaType.MEDIA) { - mOnMediaLibraryButtonListener.onMediaLibraryMediaButtonClicked(allowMultipleSelection); - } else if (mediaType == MediaType.ANY) { - mOnMediaLibraryButtonListener.onMediaLibraryFileButtonClicked(allowMultipleSelection); + switch (mediaType) { + case IMAGE: + mOnMediaLibraryButtonListener.onMediaLibraryImageButtonClicked(allowMultipleSelection); + break; + case VIDEO: + mOnMediaLibraryButtonListener.onMediaLibraryVideoButtonClicked(allowMultipleSelection); + break; + case MEDIA: + mOnMediaLibraryButtonListener.onMediaLibraryMediaButtonClicked(allowMultipleSelection); + break; + case AUDIO: + mOnMediaLibraryButtonListener.onMediaLibraryAudioButtonClicked(allowMultipleSelection); + break; + case ANY: + mOnMediaLibraryButtonListener.onMediaLibraryFileButtonClicked(allowMultipleSelection); + break; + case OTHER: + break; } } @@ -347,12 +359,21 @@ public void editorDidEmitLog(String message, LogLevel logLevel) { @Override public void getOtherMediaPickerOptions(OtherMediaOptionsReceivedCallback otherMediaOptionsReceivedCallback, MediaType mediaType) { - if (mediaType == MediaType.IMAGE || mediaType == MediaType.MEDIA) { - ArrayList otherMediaImageOptions = mOnMediaLibraryButtonListener.onGetOtherMediaImageOptions(); - otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaImageOptions); - } else if (mediaType == MediaType.ANY) { - ArrayList otherMediaFileOptions = mOnMediaLibraryButtonListener.onGetOtherMediaFileOptions(); - otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaFileOptions); + switch (mediaType){ + case IMAGE: + case MEDIA: + ArrayList otherMediaImageOptions = mOnMediaLibraryButtonListener.onGetOtherMediaImageOptions(); + otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaImageOptions); + break; + case ANY: + ArrayList otherMediaFileOptions = mOnMediaLibraryButtonListener.onGetOtherMediaFileOptions(); + otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaFileOptions); + break; + case AUDIO: + ArrayList otherMediaAudioFileOptions = mOnMediaLibraryButtonListener.onGetOtherMediaAudioFileOptions(); + otherMediaOptionsReceivedCallback.onOtherMediaOptionsReceived(otherMediaAudioFileOptions); + break; + } } diff --git a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java index 61303b1fce8ec..dd9519b4ba2cd 100644 --- a/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java +++ b/packages/react-native-editor/android/app/src/main/java/com/gutenberg/MainApplication.java @@ -79,11 +79,14 @@ public void requestMediaPickFromMediaLibrary(MediaSelectedCallback mediaSelected break; case VIDEO: rnMediaList.add(new Media(2, "https://i.cloudup.com/YtZFJbuQCE.mov", "video", "Cloudup", "")); + break; case ANY: case OTHER: rnMediaList.add(new Media(3, "https://wordpress.org/latest.zip", "zip", "WordPress latest version", "WordPress.zip")); break; - + case AUDIO: + rnMediaList.add(new Media(4, "https://cldup.com/59IrU0WJtq.mp3", "audio", "Summer presto", "")); + break; } mediaSelectedCallback.onMediaFileSelected(rnMediaList); } diff --git a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift index 5cbc0f5644c09..1e1d7841a1d49 100644 --- a/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift +++ b/packages/react-native-editor/ios/GutenbergDemo/GutenbergViewController.swift @@ -102,8 +102,8 @@ extension GutenbergViewController: GutenbergBridgeDelegate { } case .other, .any: callback([MediaInfo(id: 3, url: "https://wordpress.org/latest.zip", type: "zip", caption: "WordPress latest version", title: "WordPress.zip")]) - default: - break + case .audio: + callback([MediaInfo(id: 5, url: "https://cldup.com/59IrU0WJtq.mp3", type: "audio", caption: "Summer presto")]) } case .deviceLibrary: print("Gutenberg did request a device media picker, opening the device picker")