diff --git a/packages/common/src/models/Collection.ts b/packages/common/src/models/Collection.ts index 052c53d4fcd..2f197c510db 100644 --- a/packages/common/src/models/Collection.ts +++ b/packages/common/src/models/Collection.ts @@ -58,6 +58,7 @@ export type CollectionMetadata = { playlist_image_sizes_multihash?: string offline?: OfflineCollectionMetadata local?: boolean + release_date?: string } export type CollectionDownloadReason = { is_from_favorites: boolean } diff --git a/packages/mobile/src/components/core/ContextualMenu.tsx b/packages/mobile/src/components/core/ContextualMenu.tsx index fd9264ac1c5..cc0d2d2c0ba 100644 --- a/packages/mobile/src/components/core/ContextualMenu.tsx +++ b/packages/mobile/src/components/core/ContextualMenu.tsx @@ -26,6 +26,7 @@ export type ContextualMenuProps = { error?: boolean errorMessage?: string lastItem?: boolean + formattedValue?: string renderValue?: (value: any) => JSX.Element | null } @@ -66,7 +67,8 @@ export const ContextualMenu = (props: ContextualMenuProps) => { errorMessage, error, lastItem, - renderValue: renderValueProp + renderValue: renderValueProp, + formattedValue } = props const styles = useStyles() @@ -120,7 +122,9 @@ export const ContextualMenu = (props: ContextualMenuProps) => { /> {hasValue ? ( - {renderValue(value)} + + {renderValue(formattedValue ?? value)} + ) : null} {error && errorMessage ? ( diff --git a/packages/mobile/src/components/details-tile/DetailsTile.tsx b/packages/mobile/src/components/details-tile/DetailsTile.tsx index 0ee139f315b..691234a3584 100644 --- a/packages/mobile/src/components/details-tile/DetailsTile.tsx +++ b/packages/mobile/src/components/details-tile/DetailsTile.tsx @@ -12,9 +12,11 @@ import { getDogEarType, isPremiumContentUSDCPurchaseGated } from '@audius/common' +import moment from 'moment' import { TouchableOpacity, View } from 'react-native' import { useSelector } from 'react-redux' +import { Text as HarmonyText, IconCalendarMonth } from '@audius/harmony-native' import IconPause from 'app/assets/images/iconPause.svg' import IconPlay from 'app/assets/images/iconPlay.svg' import IconRepeat from 'app/assets/images/iconRepeatOff.svg' @@ -139,6 +141,12 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ }, link: { color: palette.primary + }, + releaseContainer: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'center', + gap: spacing(1) } })) @@ -241,12 +249,14 @@ export const DetailsTile = ({ light() onPressPreview?.() }, [onPressPreview]) - + const isScheduledRelease = track?.release_date + ? moment(track.release_date).isAfter(moment()) + : false const renderDogEar = () => { const dogEarType = getDogEarType({ isOwner, premiumConditions, - isUnlisted + isUnlisted: isUnlisted && !isScheduledRelease }) return dogEarType ? : null } @@ -401,6 +411,18 @@ export const DetailsTile = ({ /> ) : null} {showPreviewButton ? : null} + {isScheduledRelease && track?.release_date ? ( + + + + Release on{' '} + {moment + .utc(track.release_date) + .local() + .format('M/D/YY @ h:mm A')} + + + ) : null} { @@ -139,6 +142,7 @@ export const LineupTile = ({ isOwner={isOwner} isArtistPick={isArtistPick} showArtistPick={showArtistPick} + releaseDate={item?.release_date ? item.release_date : undefined} /> {children} diff --git a/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx b/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx index 06b6491470d..1d7ffb01ffe 100644 --- a/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx +++ b/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx @@ -13,9 +13,11 @@ import { favoritesUserListActions, isPremiumContentUSDCPurchaseGated } from '@audius/common' +import moment from 'moment' import { View, TouchableOpacity } from 'react-native' import { useDispatch } from 'react-redux' +import { IconCalendarMonth } from '@audius/harmony-native' import IconHeart from 'app/assets/images/iconHeart.svg' import IconHidden from 'app/assets/images/iconHidden.svg' import IconRepost from 'app/assets/images/iconRepost.svg' @@ -108,6 +110,7 @@ type Props = { isOwner: boolean isArtistPick?: boolean showArtistPick?: boolean + releaseDate?: string } export const LineupTileStats = ({ @@ -128,11 +131,12 @@ export const LineupTileStats = ({ premiumConditions, isOwner, isArtistPick, - showArtistPick + showArtistPick, + releaseDate }: Props) => { const styles = useStyles() const trackTileStyles = useTrackTileStyles() - const { neutralLight4 } = useThemeColors() + const { neutralLight4, accentPurple } = useThemeColors() const dispatch = useDispatch() const navigation = useNavigation() @@ -155,7 +159,7 @@ export const LineupTileStats = ({ ) const isReadonly = variant === 'readonly' - + const isScheduledRelease = isUnlisted && moment(releaseDate).isAfter(moment()) return ( @@ -181,7 +185,7 @@ export const LineupTileStats = ({ ) : null} - {isUnlisted ? ( + {isUnlisted && !isScheduledRelease ? ( ) : null} + {isUnlisted && isScheduledRelease ? ( + + + + Releases{' '} + {moment.utc(releaseDate).local().format('M/D/YY @ h:mm A')} + + + ) : null} {hasEngagement && !isUnlisted ? ( <> diff --git a/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx b/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx index 41bbd4d863d..3f1a7550dd7 100644 --- a/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx +++ b/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import type { UploadTrack } from '@audius/common' +import { FeatureFlags, type UploadTrack } from '@audius/common' import { Keyboard } from 'react-native' import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' import { useDispatch } from 'react-redux' @@ -13,6 +13,7 @@ import { InputErrorMessage } from 'app/components/core/InputErrorMessage' import { PickArtworkField, TextField } from 'app/components/fields' import { useNavigation } from 'app/hooks/useNavigation' import { useOneTimeDrawer } from 'app/hooks/useOneTimeDrawer' +import { useFeatureFlag } from 'app/hooks/useRemoteConfig' import { setVisibility } from 'app/store/drawers/slice' import { makeStyles } from 'app/styles' @@ -26,6 +27,7 @@ import { TagField, SubmenuList, RemixSettingsField, + ReleaseDateField, AdvancedOptionsField, AccessAndSaleField } from './fields' @@ -92,6 +94,10 @@ export const EditTrackForm = (props: EditTrackFormProps) => { } }, [dirty, navigation, dispatch]) + const { isEnabled: isScheduledReleasesEnabled } = useFeatureFlag( + FeatureFlags.SCHEDULED_RELEASES + ) + return ( <> { + {isScheduledReleasesEnabled ? : <>} diff --git a/packages/mobile/src/screens/edit-track-screen/EditTrackNavigator.tsx b/packages/mobile/src/screens/edit-track-screen/EditTrackNavigator.tsx index 10501896cde..b2b7a6f9f1c 100644 --- a/packages/mobile/src/screens/edit-track-screen/EditTrackNavigator.tsx +++ b/packages/mobile/src/screens/edit-track-screen/EditTrackNavigator.tsx @@ -1,7 +1,9 @@ +import { FeatureFlags } from '@audius/common' import { createNativeStackNavigator } from '@react-navigation/native-stack' import { GatedContentUploadPromptDrawer } from 'app/components/gated-content-upload-prompt-drawer' import { SupportersInfoDrawer } from 'app/components/supporters-info-drawer' +import { useFeatureFlag } from 'app/hooks/useRemoteConfig' import { useAppScreenOptions } from 'app/screens/app-screen/useAppScreenOptions' import { EditTrackForm } from './EditTrackForm' @@ -13,7 +15,8 @@ import { LicenseTypeScreen, RemixSettingsScreen, SelectGenreScreen, - SelectMoodScreen + SelectMoodScreen, + ReleaseDateScreen } from './screens' import { NFTCollectionsScreen } from './screens/NFTCollectionsScreen' import type { EditTrackFormProps } from './types' @@ -26,6 +29,9 @@ type EditTrackNavigatorProps = EditTrackFormProps export const EditTrackNavigator = (props: EditTrackNavigatorProps) => { const screenOptions = useAppScreenOptions(screenOptionOverrides) + const { isEnabled: isScheduledReleasesEnabled } = useFeatureFlag( + FeatureFlags.SCHEDULED_RELEASES + ) return ( <> @@ -36,6 +42,9 @@ export const EditTrackNavigator = (props: EditTrackNavigatorProps) => { + {isScheduledReleasesEnabled ? ( + + ) : null} moment(date).isSame(moment(), 'day') +import { ContextualMenu } from 'app/components/core' const messages = { label: 'Release Date', today: 'Today' } -const useStyles = makeStyles(({ spacing }) => ({ - root: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: spacing(6), - paddingVertical: spacing(2) - }, - dateText: { - textTransform: 'uppercase' - }, - datePickerModal: { - // Specific padding to hide the underlying "done" button with the "cancel" button - paddingBottom: 37 - } -})) +export const ReleaseDateField = (props) => { + const [{ value }] = useField>('release_date') -const ConfirmDateButton = (props: CustomConfirmButtonPropTypes) => { - const { label, onPress } = props return ( -