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")