From aadc5119a7a2e76b55f35698423645626e66cf50 Mon Sep 17 00:00:00 2001 From: etoledom Date: Mon, 1 Feb 2021 16:36:01 +0100 Subject: [PATCH] [RNMobile] Audio Player UI for audio block (#27467) * Basics of Audio block working * Add audio support to MediaUpload * Add handling of file uploads and replace * WPMediaLibrary support for Audio block * Avoid removing media info on error state * Linting * Added an AUDIO file to the test requestMediaPickFromMediaLibrary func * Fixed typo in ToolbarButton of Audio Block. * Removed auto help behavior present on web that's not used on mobile. * [Android] Wired the click of the Audio Media Library button. * Added Audio media options for choosing audio file locally. * [RNMobile] Audio Block: Proper caption field (#27689) * Audio Player UI for audio block * Add extension to styles import * Show file name while loading and retry message on error * Pass state props to audio player component * Implement placeholder-ish player UI structure * added styles for icon, title and subtitle * added blue-wordpress as link color. * added sizing based on design specs. * Fixed object destructuring error. * added icon styling state for upload in progress or upload failed. * implemented error style and behavior. * added styling for upload failed text. * Override MediaUploadProgress styles * Update filename extension spseration to handle two dots * Update UI structure and styles * Make retry message translateable * Update snaphots * Fix lint error * Set initial file name * Remove devOnly flag from audio block * Increase tap target of button * Add 1px between title and subtitle * Align title, subtitle and button vertically * On iOS use VideoPlayer to play audio files in-app * Revert "On iOS use VideoPlayer to play audio files in-app" This reverts commit b1eb8dd5d110b4243d3f71e0121bab92266151ec. * [RNMobile] Audio Block - Cancel and Retry Dialog (#28540) Co-authored-by: Ceyhun Ozugur * Rename button title * Add pill-shaped background to button * Add padding to title * Fix file title being empty when selecting from media library * Use safeDecodeURI * On iOS use VideoPlayer to play audio files in-app * Decrease button font size Co-authored-by: Joel Dean * Revert "Remove devOnly flag from audio block" This reverts commit e6b5b6df7b28570fc6bfbe28239eff16a5e3039d. * Add right padding to title container to fix error state Co-authored-by: Joel Dean Co-authored-by: Ceyhun Ozugur --- packages/base-styles/_colors.native.scss | 2 + .../media-upload-progress/index.native.js | 16 +- .../block-library/src/audio/edit.native.js | 34 ++- .../block-library/src/audio/style.native.scss | 13 + .../test/__snapshots__/edit.native.js.snap | 14 ++ packages/components/src/index.native.js | 1 + .../src/mobile/audio-player/index.native.js | 225 ++++++++++++++++++ .../src/mobile/audio-player/styles.scss | 114 +++++++++ .../components/src/spinner/index.native.js | 2 +- 9 files changed, 408 insertions(+), 13 deletions(-) create mode 100644 packages/block-library/src/audio/style.native.scss create mode 100644 packages/components/src/mobile/audio-player/index.native.js create mode 100644 packages/components/src/mobile/audio-player/styles.scss diff --git a/packages/base-styles/_colors.native.scss b/packages/base-styles/_colors.native.scss index 3e63958cc4624a..059d0580e4badb 100644 --- a/packages/base-styles/_colors.native.scss +++ b/packages/base-styles/_colors.native.scss @@ -24,7 +24,9 @@ $light-opacity-light-700: rgba($white, 0.4); $alert-yellow: #f0b849; $alert-red: #d94f4f; $alert-green: #4ab866; +$red-30: #f86368; $red-40: #e65054; +$red-50: #d63638; // Primary Accent (Blues) $blue-wordpress: #0087be; diff --git a/packages/block-editor/src/components/media-upload-progress/index.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js index 97c9bef5ef9514..386516b097be28 100644 --- a/packages/block-editor/src/components/media-upload-progress/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/index.native.js @@ -128,12 +128,24 @@ export class MediaUploadProgress extends Component { const progressBarStyle = [ styles.progressBar, showSpinner || styles.progressBarHidden, + this.props.progressBarStyle, ]; return ( - + - { showSpinner && } + { showSpinner && ( + + ) } { renderContent( { isUploadInProgress, diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 3e66485f936991..e6dc50330ce573 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, TouchableWithoutFeedback } from 'react-native'; +import { TouchableWithoutFeedback } from 'react-native'; import { isEmpty } from 'lodash'; /** @@ -15,6 +15,7 @@ import { withNotices, ToolbarButton, ToolbarGroup, + AudioPlayer, } from '@wordpress/components'; import { BlockCaption, @@ -29,6 +30,11 @@ import { __, sprintf } from '@wordpress/i18n'; import { audio as icon, replace } from '@wordpress/icons'; import { useState } from '@wordpress/element'; +/** + * Internal dependencies + */ +import styles from './style.scss'; + const ALLOWED_MEDIA_TYPES = [ 'audio' ]; function AudioEdit( { @@ -70,7 +76,6 @@ function AudioEdit( { 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 @@ -133,17 +138,26 @@ function AudioEdit( { onFinishMediaUploadWithSuccess={ onFileChange } onFinishMediaUploadWithFailure={ onError } onMediaUploadStateReset={ onFileChange } - renderContent={ ( { isUploadInProgress, isUploadFailed } ) => { + containerStyle={ styles.progressContainer } + progressBarStyle={ styles.progressBar } + spinnerStyle={ styles.spinner } + renderContent={ ( { + isUploadInProgress, + isUploadFailed, + retryMessage, + } ) => { return ( - + <> { ! isCaptionSelected && getBlockControls( open ) } { getMediaOptions() } - - ⏯ Audio Player goes here.{ ' ' } - { isUploadInProgress && 'Uploading...' } - { isUploadFailed && 'ERROR' } - - + + ); } } /> diff --git a/packages/block-library/src/audio/style.native.scss b/packages/block-library/src/audio/style.native.scss new file mode 100644 index 00000000000000..79a428acb06b93 --- /dev/null +++ b/packages/block-library/src/audio/style.native.scss @@ -0,0 +1,13 @@ +.progressContainer { + border-radius: 4px; + overflow: hidden; +} + +.progressBar { + height: 4px; + margin-bottom: -4px; +} + +.spinner { + height: 4px; +} diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap index a4f88395a19e90..ceb74a27f74922 100644 --- a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -3,12 +3,19 @@ exports[`File block renders file error state without crashing 1`] = ` @@ -195,12 +202,19 @@ exports[`File block renders file error state without crashing 1`] = ` exports[`File block renders file without crashing 1`] = ` diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 6c96e9e646b91a..0fee6fc26bad96 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -83,6 +83,7 @@ export { default as ImageEditingButton } from './mobile/image/image-editing-butt export { default as InserterButton } from './mobile/inserter-button'; export { setClipboard, getClipboard } from './mobile/clipboard'; export { default as Preview } from './mobile/preview'; +export { default as AudioPlayer } from './mobile/audio-player'; // Utils export { colorsUtils } from './mobile/color-settings/utils'; diff --git a/packages/components/src/mobile/audio-player/index.native.js b/packages/components/src/mobile/audio-player/index.native.js new file mode 100644 index 00000000000000..ce01b815ebcd82 --- /dev/null +++ b/packages/components/src/mobile/audio-player/index.native.js @@ -0,0 +1,225 @@ +/** + * External dependencies + */ +import { + Text, + TouchableWithoutFeedback, + Linking, + Alert, + Platform, +} from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * WordPress dependencies + */ +import { View } from '@wordpress/primitives'; +import { Icon } from '@wordpress/components'; +import { withPreferredColorScheme } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; +import { audio, warning } from '@wordpress/icons'; +import { + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, +} from '@wordpress/react-native-bridge'; +import { getProtocol, safeDecodeURI } from '@wordpress/url'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +const isIOS = Platform.OS === 'ios'; + +function Player( { + getStylesFromColorScheme, + isUploadInProgress, + isUploadFailed, + attributes, + isSelected, +} ) { + const { id, src } = attributes; + const [ paused, setPaused ] = useState( true ); + + const onPressListen = () => { + if ( src ) { + if ( isIOS && this.player ) { + this.player.presentFullscreenPlayer(); + return; + } + + Linking.canOpenURL( src ) + .then( ( supported ) => { + if ( ! supported ) { + Alert.alert( + __( 'Problem opening the audio' ), + __( 'No application can handle this request.' ) + ); + } else { + return Linking.openURL( src ); + } + } ) + .catch( () => { + Alert.alert( + __( 'Problem opening the audio' ), + __( 'An unknown error occurred. Please try again.' ) + ); + } ); + } + }; + + const containerStyle = getStylesFromColorScheme( + styles.container, + styles.containerDark + ); + + const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark ); + + const iconDisabledStyle = getStylesFromColorScheme( + styles.iconDisabled, + styles.iconDisabledDark + ); + + const isDisabled = isUploadFailed || isUploadInProgress; + + const finalIconStyle = { + ...iconStyle, + ...( isDisabled && iconDisabledStyle ), + }; + + const iconContainerStyle = getStylesFromColorScheme( + styles.iconContainer, + styles.iconContainerDark + ); + + const titleContainerStyle = { + ...styles.titleContainer, + ...( isIOS ? styles.titleContainerIOS : styles.titleContainerAndroid ), + }; + + const titleStyle = getStylesFromColorScheme( + styles.title, + styles.titleDark + ); + + const uploadFailedStyle = getStylesFromColorScheme( + styles.uploadFailed, + styles.uploadFailedDark + ); + + const subtitleStyle = getStylesFromColorScheme( + styles.subtitle, + styles.subtitleDark + ); + + const finalSubtitleStyle = { + ...subtitleStyle, + ...( isUploadFailed && uploadFailedStyle ), + }; + + const buttonBackgroundStyle = getStylesFromColorScheme( + styles.buttonBackground, + styles.buttonBackgroundDark + ); + + let title = ''; + let extension = ''; + + if ( src ) { + const decodedURI = safeDecodeURI( src ); + const fileName = decodedURI.split( '/' ).pop(); + const parts = fileName.split( '.' ); + extension = parts.pop().toUpperCase(); + title = parts.join( '.' ); + } + + const getSubtitleValue = () => { + if ( isUploadInProgress ) { + return __( 'Uploading…' ); + } + if ( isUploadFailed ) { + return __( 'Failed to insert audio file. Please tap for options.' ); + } + return ( + extension + + // translators: displays audio file extension. e.g. MP3 audio file + __( ' audio file' ) + ); + }; + + function onAudioUploadCancelDialog() { + if ( isUploadInProgress ) { + requestImageUploadCancelDialog( id ); + } else if ( id && getProtocol( src ) === 'file:' ) { + requestImageFailedRetryDialog( id ); + } + } + + return ( + + + + + + + { title } + + { isUploadFailed && ( + + ) } + + { getSubtitleValue() } + + + + { ! isDisabled && ( + + + + { __( 'OPEN' ) } + + + + ) } + { isIOS && ( + { + this.player = ref; + } } + controls={ false } + ignoreSilentSwitch={ 'ignore' } + onFullscreenPlayerWillPresent={ () => { + setPaused( false ); + } } + onFullscreenPlayerDidDismiss={ () => { + setPaused( true ); + } } + /> + ) } + + + ); +} + +export default withPreferredColorScheme( Player ); diff --git a/packages/components/src/mobile/audio-player/styles.scss b/packages/components/src/mobile/audio-player/styles.scss new file mode 100644 index 00000000000000..6dbdb7c09c5f54 --- /dev/null +++ b/packages/components/src/mobile/audio-player/styles.scss @@ -0,0 +1,114 @@ +.container { + background-color: $light-ultra-dim; + border-radius: 4px; + flex-direction: row; + align-items: center; +} + +.containerDark { + background-color: $dark-ultra-dim; +} + +@mixin circle($width) { + width: $width; + height: $width; + border-radius: $width/2; +} + +.iconContainer { + background-color: $light-ultra-dim; + margin: 12px 16px; + align-items: center; + justify-content: center; + @include circle(37px); +} + +.iconContainerDark { + background-color: $dark-ultra-dim; +} + +.icon { + color: $light-secondary; +} + +.iconDark { + color: $dark-secondary; +} + +.iconDisabled { + color: $light-quaternary; +} + +.iconDisabledDark { + color: $dark-quaternary; +} + +.disabledIcon { + color: $black; +} + +.titleContainer { + padding: 12px 16px 12px 0; + flex: 1; +} + +.titleContainerIOS { + margin-top: -1px; +} + +.titleContainerAndroid { + margin-top: -2px; +} + +.title { + font-size: 17; + margin-bottom: 1px; + color: $light-primary; +} + +.titleDark { + color: $dark-primary; +} + +.subtitleContainer { + flex-direction: row; + align-items: center; +} + +.subtitle { + font-size: 12; + color: $light-secondary; +} + +.subtitleDark { + color: $dark-secondary; +} + +.buttonBackground { + margin: 12px 16px; + padding: 8px 16px; + border-radius: 500px; + background-color: $light-ultra-dim; +} + +.buttonBackgroundDark { + background-color: $dark-ultra-dim; +} + +.buttonText { + font-size: 14; + font-weight: 500; + color: $blue-wordpress; +} + +.errorIcon { + margin-right: 4px; +} + +.uploadFailed { + color: $red-50; +} + +.uploadFailedDark { + color: $red-30; +} diff --git a/packages/components/src/spinner/index.native.js b/packages/components/src/spinner/index.native.js index 5b25cf7945c079..a4a64f280a29ba 100644 --- a/packages/components/src/spinner/index.native.js +++ b/packages/components/src/spinner/index.native.js @@ -13,5 +13,5 @@ export default function Spinner( props ) { const width = progress + '%'; - return ; + return ; }