Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const motionUsageDescription =
'Soundlog가 이동 중인 여행의 위치 기록을 안정적으로 제공하기 위해 기기의 동작 상태에 접근할 수 있습니다.';

const baseConfig = {
name: 'Soundlog',
slug: 'soundlog',
Expand All @@ -16,6 +19,7 @@ const baseConfig = {
'Soundlog가 저장된 로그인 정보를 안전하게 보호하기 위해 Face ID를 사용할 수 있습니다.',
NSLocationWhenInUseUsageDescription:
'Soundlog가 현재 장소에 맞는 음악을 추천하고 여행 순간의 위치를 기록하기 위해 위치 권한이 필요합니다.',
NSMotionUsageDescription: motionUsageDescription,
NSPhotoLibraryAddUsageDescription:
'Soundlog가 리캡 이미지를 사진 보관함에 저장하기 위해 권한이 필요합니다.',
NSPhotoLibraryUsageDescription:
Expand Down Expand Up @@ -139,7 +143,7 @@ const baseConfig = {
'Soundlog가 현재 장소에 맞는 음악을 추천하고 여행 순간의 위치를 기록하기 위해 위치 권한이 필요합니다.',
locationAlwaysAndWhenInUsePermission: false,
locationAlwaysPermission: false,
motionUsagePermission: false,
motionUsagePermission: motionUsageDescription,
isIosBackgroundLocationEnabled: false,
isAndroidBackgroundLocationEnabled: false,
isAndroidForegroundServiceEnabled: false,
Expand Down
2 changes: 1 addition & 1 deletion app/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export default function LoginScreen() {
}`}
>
{hasAcceptedRequiredTerms ? (
<Feather color="#050916" name="check" size={15} />
<Feather color="#4A1D96" name="check" size={15} />
) : null}
</View>
<AppText className="min-w-0 flex-1 text-xs leading-5 text-white/65">
Expand Down
2 changes: 1 addition & 1 deletion docs/deployment/IOS_PHYSICAL_DEVICE_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ v1.0은 iPhone 전용으로 제출한다. iPad 스크린샷 자산과 iPad 실

### 출시 권한 및 개인정보 선언

production 설정은 사진 기록에 필요한 카메라와 사진 보관함, 현재 장소와 여행 기록에 필요한 `When In Use` 위치 권한만 요청한다. 녹음, 항상 위치, 모션 활동, iOS/Android 백그라운드 위치 권한은 사용하지 않는다.
production 설정은 사진 기록에 필요한 카메라와 사진 보관함, 현재 장소와 여행 기록에 필요한 `When In Use` 위치 권한만 직접 요청한다. 녹음, 항상 위치, 모션 활동, iOS와 Android의 백그라운드 위치 권한은 앱 기능에서 사용하지 않는다. 다만 ExpoLocation 바이너리가 CoreMotion API를 포함하므로 Apple 업로드 검사를 통과하기 위한 `NSMotionUsageDescription` 문구는 `Info.plist`에 유지한다. 이 문구만으로 사용자에게 모션 권한을 요청하지는 않는다.

`ios.privacyManifests`에는 계정 이름·이메일·사용자 ID, 정확한 위치, 사진 또는 비디오, 사용자 콘텐츠를 앱 기능 목적으로 선언하고, 추천·저장·공유 등 앱 내 상호작용은 분석 목적으로 선언한다. 모든 선언은 계정과 연결되지만 추적에는 사용하지 않는다. App Store Connect의 App Privacy 응답도 이 설정 및 실제 서버 처리와 동일하게 입력한다.

Expand Down
20 changes: 18 additions & 2 deletions scripts/check-store-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,21 @@ function assertRuntimePermissionPlugins(config) {
addError('expo-location must retain an iOS When In Use location permission message.');
}

if (!config.ios?.infoPlist?.NSMotionUsageDescription?.trim()) {
addError(
'Expo config must include NSMotionUsageDescription because ExpoLocation links CoreMotion APIs.',
);
}

if (location.motionUsagePermission !== config.ios?.infoPlist?.NSMotionUsageDescription) {
addError(
'expo-location motionUsagePermission must match the iOS NSMotionUsageDescription.',
);
}

[
'locationAlwaysAndWhenInUsePermission',
'locationAlwaysPermission',
'motionUsagePermission',
'isIosBackgroundLocationEnabled',
'isAndroidBackgroundLocationEnabled',
'isAndroidForegroundServiceEnabled',
Expand Down Expand Up @@ -297,7 +308,6 @@ function assertNativeIosPlist() {
'NSLocationAlwaysUsageDescription',
'NSLocationAlwaysAndWhenInUseUsageDescription',
'NSMicrophoneUsageDescription',
'NSMotionUsageDescription',
];

forbiddenKeys.forEach((key) => {
Expand All @@ -306,6 +316,12 @@ function assertNativeIosPlist() {
}
});

if (!/<key>NSMotionUsageDescription<\/key>\s*<string>[^<]+<\/string>/.test(plist)) {
addError(
'iOS native Info.plist must include a non-empty NSMotionUsageDescription because ExpoLocation links CoreMotion APIs.',
);
}

if (plist.includes('Expo Dev Launcher')) {
const projectPath = path.join(
projectRoot,
Expand Down
2 changes: 1 addition & 1 deletion src/api/homeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const homeApi = {
const playlists = await requestApi<FeaturedPlaylist[]>('/v1/home/featured-playlists', {
query: {
lat: params?.location?.lat,
limit: 10,
limit: 20,
lng: params?.location?.lng,
locationRecommendationEnabled: params?.locationRecommendationEnabled ?? false,
placeId: params?.place?.id,
Expand Down
16 changes: 13 additions & 3 deletions src/components/AppText.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { Text, TextProps } from 'react-native';
import { StyleSheet, Text, TextProps } from 'react-native';

export function AppText({ className = '', ...props }: TextProps & { className?: string }) {
return <Text className={className} {...props} />;
const textColorClassPattern = /(?:^|\s)text-(?:\[#|(?:black|white|transparent)(?:\/\d+)?(?:\s|$)|(?:amber|blue|cyan|emerald|fuchsia|gray|green|indigo|lime|neutral|orange|pink|purple|red|rose|sky|slate|stone|teal|violet|yellow|zinc)-\d+(?:\/\d+)?(?:\s|$)|soundlog-[\w-]+(?:\/\d+)?(?:\s|$))/;

export function AppText({
className = '',
style,
...props
}: TextProps & { className?: string }) {
const hasExplicitColor =
textColorClassPattern.test(className) || Boolean(StyleSheet.flatten(style)?.color);
const resolvedClassName = hasExplicitColor ? className : `text-white ${className}`.trim();

return <Text className={resolvedClassName} style={style} {...props} />;
}
2 changes: 1 addition & 1 deletion src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function EmptyState({ action, description, title }: EmptyStateProps) {
) : null}
{action ? (
<Pressable className="mt-6 rounded-full bg-white px-5 py-3" onPress={action.onPress}>
<AppText className="font-semibold text-[#050916]">{action.label}</AppText>
<AppText className="font-semibold text-soundlog-inverse">{action.label}</AppText>
</Pressable>
) : null}
</View>
Expand Down
4 changes: 2 additions & 2 deletions src/components/MiniPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,8 @@ export function MiniPlayer() {
className="h-12 flex-row items-center justify-center gap-2 rounded-full bg-white"
onPress={handleCaptureMoment}
>
<Feather color="#050916" name="camera" size={17} />
<AppText className="text-sm font-semibold text-[#050916]">
<Feather color="#4A1D96" name="camera" size={17} />
<AppText className="text-sm font-semibold text-soundlog-inverse">
이 곡으로 기록
</AppText>
</Pressable>
Expand Down
2 changes: 1 addition & 1 deletion src/components/dev/DevTestManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function ManagerButton({
: destructive
? 'rgba(248,113,113,0.28)'
: 'rgba(255,255,255,0.12)';
const color = active ? '#050916' : destructive ? '#fecaca' : 'rgba(255,255,255,0.78)';
const color = active ? '#4A1D96' : destructive ? '#fecaca' : 'rgba(255,255,255,0.78)';

return (
<Pressable
Expand Down
20 changes: 18 additions & 2 deletions src/components/home/FeaturedPlaylistCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Pressable, View } from 'react-native';
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { Pressable, StyleSheet, View } from 'react-native';

import { AppText } from '@/components/AppText';
import { Chip } from '@/components/Chip';
Expand All @@ -17,7 +19,21 @@ export function FeaturedPlaylistCard({ onPress, playlist }: FeaturedPlaylistCard
className="mr-4 h-[260px] w-[180px] justify-end overflow-hidden rounded-[8px] border border-white/10 bg-soundlog-card p-4"
onPress={() => onPress(playlist)}
>
<View className="absolute inset-0 bg-black/10" />
{playlist.coverImageUrl ? (
<Image
accessibilityIgnoresInvertColors
contentFit="cover"
source={{ uri: playlist.coverImageUrl }}
style={StyleSheet.absoluteFill}
transition={250}
/>
) : null}
<LinearGradient
colors={['rgba(5,9,22,0.04)', 'rgba(5,9,22,0.88)']}
end={{ x: 0.5, y: 1 }}
start={{ x: 0.5, y: 0.2 }}
style={StyleSheet.absoluteFill}
/>
<View className="absolute left-3 top-4 flex-row gap-2">
<Chip label={`${playlist.trackCount}곡`} size="small" />
<Chip label={playlist.durationText} size="small" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/home/HomeSoundtrackBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ export function HomeSoundtrackBottomSheet({
className="mt-4 min-h-11 items-center justify-center rounded-full bg-[#B7E628] px-6"
onPress={onRetry}
>
<AppText className="text-sm font-semibold text-[#07101E]">
<AppText className="text-sm font-semibold text-soundlog-inverse">
다시 시도
</AppText>
</Pressable>
Expand Down
4 changes: 2 additions & 2 deletions src/components/home/MusicLogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ export function MusicLogCard({
</View>

<AppText
className={`text-[12px] font-semibold ${hasImage ? 'text-white' : 'text-[#111827]'}`}
className={`text-[12px] font-semibold ${hasImage ? 'text-white' : 'text-soundlog-inverse'}`}
numberOfLines={1}
>
{item.placeName}
</AppText>
<AppText
className={`text-[10px] ${hasImage ? 'text-white/70' : 'text-[#4b5563]'}`}
className={`text-[10px] ${hasImage ? 'text-white/70' : 'text-[#6D42B8]'}`}
numberOfLines={1}
>
{item.trackTitle}
Expand Down
14 changes: 7 additions & 7 deletions src/components/home/TravelModeHomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ function TravelStartPanel({
className="mt-5 h-[132px] w-[132px] self-center items-center justify-center rounded-full bg-soundlog-lime"
onPress={onStartTravel}
>
<Feather color="#050916" name="navigation" size={24} />
<AppText className="mt-2 text-[24px] font-semibold text-[#050916]">
<Feather color="#4A1D96" name="navigation" size={24} />
<AppText className="mt-2 text-[24px] font-semibold text-soundlog-inverse">
여행 시작
</AppText>
</Pressable>
Expand Down Expand Up @@ -384,7 +384,7 @@ function ActiveRecommendationPanel({
<View className="min-w-0 flex-1">
<View className="flex-row flex-wrap gap-2">
<View className="rounded-full bg-soundlog-lime px-3 py-1.5">
<AppText className="text-[11px] font-semibold text-[#050916]">
<AppText className="text-[11px] font-semibold text-soundlog-inverse">
여행 기록 중
</AppText>
</View>
Expand Down Expand Up @@ -444,13 +444,13 @@ function ActiveRecommendationPanel({
onPress={needsLocation && !playlist ? onRefreshLocation : onOpenPlaylist}
>
<Feather
color={playlistButtonDisabled ? 'rgba(255,255,255,0.42)' : '#050916'}
color={playlistButtonDisabled ? 'rgba(255,255,255,0.42)' : '#4A1D96'}
name={needsLocation && !playlist ? 'crosshair' : 'disc'}
size={16}
/>
<AppText
className={`text-xs font-semibold ${
playlistButtonDisabled ? 'text-white/40' : 'text-[#050916]'
playlistButtonDisabled ? 'text-white/40' : 'text-soundlog-inverse'
}`}
>
{isOpeningPlaylist ? '여는 중' : needsLocation && !playlist ? '위치 잡기' : '곡 보기'}
Expand Down Expand Up @@ -493,8 +493,8 @@ function ActiveRecommendationPanel({
className="h-11 flex-1 flex-row items-center justify-center gap-2 rounded-full bg-soundlog-warning"
onPress={onEndTravel}
>
<Feather color="#050916" name="stop-circle" size={14} />
<AppText className="text-sm font-semibold text-[#050916]">여행 종료</AppText>
<Feather color="#4A1D96" name="stop-circle" size={14} />
<AppText className="text-sm font-semibold text-soundlog-inverse">여행 종료</AppText>
</Pressable>
</View>
</View>
Expand Down
2 changes: 1 addition & 1 deletion src/components/home/TravelModeSuggestionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function TravelModeSuggestionSheet({
onPress={onStartTravelMode}
style={{ backgroundColor: '#F5A451' }}
>
<AppText className="text-sm font-semibold" style={{ color: '#07101E' }}>
<AppText className="text-sm font-semibold" style={{ color: '#4A1D96' }}>
Travel Mode 시작
</AppText>
</Pressable>
Expand Down
2 changes: 1 addition & 1 deletion src/components/library/LibraryScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export function LibraryScreen() {
<AppText
className="text-sm font-semibold"
style={{
color: isSelected ? "#050916" : "rgba(255,255,255,0.65)",
color: isSelected ? "#4A1D96" : "rgba(255,255,255,0.65)",
}}
>
{tab.label}
Expand Down
2 changes: 1 addition & 1 deletion src/components/moment-capture/CameraCaptureView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function CameraCaptureView({
style={{ opacity: controlsDisabled ? 0.7 : 1 }}
>
{isCapturing || isPickingPhoto ? (
<ActivityIndicator color="#050916" />
<ActivityIndicator color="#4A1D96" />
) : (
<View className="h-[60px] w-[60px] rounded-full bg-white" />
)}
Expand Down
24 changes: 12 additions & 12 deletions src/components/moment-capture/MomentPhotoCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ export const MomentPhotoCanvas = forwardRef<
{isMapTemplate ? (
<View className="flex-row items-center gap-3">
<View className="h-8 w-8 items-center justify-center rounded-full bg-soundlog-lime">
<Feather color="#050916" name="map-pin" size={15} />
<Feather color="#4A1D96" name="map-pin" size={15} />
</View>
<View className="min-w-0 flex-1">
<AppText className="text-sm font-semibold text-white">
Expand Down Expand Up @@ -651,7 +651,7 @@ function MapMusicPin({ track }: { track?: Track }) {
{ backgroundColor: track?.fallbackColor ?? '#B7E628' },
]}
>
<Feather color="#050916" name="music" size={16} />
<Feather color="#4A1D96" name="music" size={16} />
</View>
)}
<View style={styles.mapMusicText}>
Expand All @@ -665,7 +665,7 @@ function MapMusicPin({ track }: { track?: Track }) {
</View>
<View style={styles.mapPinConnector} />
<View style={styles.mapPinHead}>
<Feather color="#050916" name="music" size={15} />
<Feather color="#4A1D96" name="music" size={15} />
</View>
<View style={styles.mapPinPoint} />
</View>
Expand Down Expand Up @@ -977,7 +977,7 @@ function StickerSegment<T extends string,>({
<AppText
className="text-xs font-semibold"
style={{
color: selected ? '#050916' : 'rgba(255,255,255,0.6)',
color: selected ? '#4A1D96' : 'rgba(255,255,255,0.6)',
}}
>
{option.label}
Expand Down Expand Up @@ -1065,12 +1065,12 @@ function getStickerThemeStyle(theme: StickerTheme) {
backgroundColor: 'rgba(183,230,40,0.94)',
borderColor: 'rgba(255,255,255,0.42)',
},
dateText: { color: 'rgba(5,9,22,0.72)' },
dateText: { color: 'rgba(74,29,150,0.82)' },
divider: { backgroundColor: 'rgba(5,9,22,0.28)' },
iconBubble: { backgroundColor: 'rgba(5,9,22,0.13)' },
iconColor: 'rgba(5,9,22,0.78)',
metaText: { color: 'rgba(5,9,22,0.66)' },
timeText: { color: '#050916' },
iconColor: 'rgba(74,29,150,0.9)',
metaText: { color: 'rgba(74,29,150,0.78)' },
timeText: { color: '#4A1D96' },
};
}

Expand All @@ -1080,12 +1080,12 @@ function getStickerThemeStyle(theme: StickerTheme) {
backgroundColor: 'rgba(255,255,255,0.92)',
borderColor: 'rgba(255,255,255,0.66)',
},
dateText: { color: 'rgba(5,9,22,0.6)' },
dateText: { color: 'rgba(74,29,150,0.76)' },
divider: { backgroundColor: 'rgba(5,9,22,0.22)' },
iconBubble: { backgroundColor: 'rgba(5,9,22,0.08)' },
iconColor: 'rgba(5,9,22,0.64)',
metaText: { color: 'rgba(5,9,22,0.5)' },
timeText: { color: '#050916' },
iconColor: 'rgba(74,29,150,0.82)',
metaText: { color: 'rgba(74,29,150,0.68)' },
timeText: { color: '#4A1D96' },
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/moment-capture/MomentReviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export const MomentReviewPanel = forwardRef<
style={{ opacity: isSaving ? 0.72 : 1 }}
>
{isSaving ? (
<ActivityIndicator color="#050916" />
<ActivityIndicator color="#4A1D96" />
) : (
<AppText className="font-semibold text-soundlog-inverse">
이 리캡 저장하기
Expand Down
2 changes: 1 addition & 1 deletion src/components/playlist/PlaylistHeroInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function PlaylistHeroInfo({
onPress={onOpenFirstTrack}
style={{ opacity: disabled ? 0.45 : 1 }}
>
<Feather color="#050916" name="music" size={22} />
<Feather color="#4A1D96" name="music" size={22} />
</Pressable>
</View>

Expand Down
2 changes: 1 addition & 1 deletion src/components/playlist/PlaylistMusicFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function PlaylistMusicFilter({
/>
<AppText
className={`text-xs font-semibold ${
isSelected ? 'text-[#050916]' : 'text-white/85'
isSelected ? 'text-soundlog-inverse' : 'text-white/85'
}`}
numberOfLines={1}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/recap-share/RecapMusicSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function RecapMusicSummary({
className="h-10 w-10 items-center justify-center rounded-full bg-soundlog-lime"
onPress={onOpenMap}
>
<Feather color="#050916" name="map" size={17} />
<Feather color="#4A1D96" name="map" size={17} />
</Pressable>
) : null
}
Expand Down
Loading
Loading