728x90
반응형

깃랩에 있는 레퍼지토리를 깃허브로 옮길일이 생겼다.

미러링을 걸어놔서 신경을 안쓰고있었는데,

이틀전부터 미러링이 안되고있었다.

 

원인을 찾아보니, 100mb 이상의 커밋이 발생하여 해당 미러링이 안된것으로 보였다.

 

일단 github 에 새로운 레파지토리를 만들고

 

git clone --mirror [클론할 레포지토리]

 

로 커밋메세지등을 클론했다.

 

그 후에, 

 

https://rtyley.github.io/bfg-repo-cleaner/

 

BFG Repo-Cleaner by rtyley

$ bfg --strip-blobs-bigger-than 100M --replace-text banned.txt repo.git an alternative to git-filter-branch The BFG is a simpler, faster alternative to git-filter-branch for cleansing bad data out of your Git repository history: Removing Crazy Big Files Re

rtyley.github.io

 

링크에서 우측

을 통해 커밋로그를 삭제하는 파일을 받아준다.

 

해당 파일을 클론한 레포지토리의 루트에 위치 시켜주고

 

 

 java -jar bfg-1.14.0.jar --strip-blobs-bigger-than 100M [클론한 레퍼지토리.git]

 

를 통해 100M 이상의 커밋 로그를 삭제해준뒤

 

같은 루트에서 cd [클론한 레퍼지토리.git]

 

을 한후, 

 

git push [옮길 레퍼지토리] -f --mirror

 

를 통해 레퍼지토리로 옮겨줬다.

 

후에 다시 gitlab에서 웹훅을 걸어주었다.

728x90
반응형
728x90
반응형

 

이커머스 앱을 준비하면서 당연히 들어가야할 기능중 하나인 주소지 관리를 개발하면서

 

다음 postcode 를 사용하였다.  rn에서 사용하려면 react-native-webview 라이브러리가 설치되어있어야한다.

 

yarn add @actbase/react-daum-postcode

 

yarn add react-native-webview

 

import Postcode from '@actbase/react-daum-postcode';
<Postcode
style={{
width: '100%',
height: '100%',
}}
onError={(e) => console.log(e)}
jsOptions={{ animation: true }}
onSelected={(data) => {
setPostVisible(false);
setZipcode(String(data?.zonecode));
setAddress(data?.address);
console.log(JSON.stringify(data));
}}
/>

 

이런식으로 사용하면된다.

728x90
반응형
728x90
반응형

프론트에서 직접 스토리지에 접근하는건 별로지만,

 

같이 일하는 서버개발자분이 앱에서 바로 S3에 업로드를 부탁하셨다.

 

워낙 라이브러리가 잘되어있어서 업로드를 하는데

 

error code :AccessControlListNotSupported

error message :The bucket does not allow ACLs

 

이라는 에러가 나왔다.

 

AWS 해당 버킷에 들어가서

 

 

편집을 누른 후

 

활성하하면 해결된다.

728x90
반응형
728x90
반응형

jwt decode 를 할 일이 생겼다.

 

yarn add jwt-decode

 

라이브러리를 사용하거나,

 

const parseJwt = (token) => {
try {
return JSON.parse(atob(token.split('.')[1]));
} catch (e) {
console.log('e :::::', e);
return null;
}
};

 

함수를 사용하면되는데,

 

invalid base64 for part #2 (Property 'atob' doesn't exist)

 

라는 에러가 나왔다.

 

app.tsx에서

 

import { decode, encode } from 'base-64';

if (!global.btoa) {
global.btoa = encode;
}

if (!global.atob) {
global.atob = decode;
}
 

 

로 atob / btoa 를 선언해주면 끝

728x90
반응형
728x90
반응형

RN을 하면서 수만가지의 에러를 만났지만, 아직도 처음보는 상황이 많다.

 

xcode로 빌드시, 빌드는 잘되어 index.js 가 돌기시작하고나서,

다음 화면으로 안나가는 일명 'stuck in splash screen' 형태가 나와서

xcode 를 살펴보니, Running 부분이 Paused로 변경되어있었다.

 

정말 감이 안오는 부분이라, 커뮤니티에 물어봤는데,

해당 부분의 태그를 클릭하여

break point 를 풀어주면 정상작동한다.

728x90
반응형
728x90
반응형

animated value를 사용시에 항상

 

const animatedY = useRef(new Animated.Value(0)).current;

이런식으로 useRef를 사용한다.

이는 데이터영역의 state 값들이 변화시에 컴포넌트의 리랜더링이 필수적으로 일어날수밖에 없는 상황에 

애니메이션값을 초기화시키지 않기 위함이다.

 

현재 구현한 애니메이션은 스크롤에 따라 opacity 가 변하고, 헤더의 위치및 높이가 변하고 등등

 

animated.timing 함수를 사용하는 부분과, Animated event 를 사용하는 부분으로 나누어져있다.

 

가끔 0번째에서 첫번째로 넘어갈때, 애니메이션이 끊기는 현상이 발생하여 디버깅해보니,

리랜더링시에 애니메이션이 초기화되서 그런가 싶었던 이유와는 별개로, nativeDriver를 사용하는 쓰레드에서 에러가 생기는것 같았다.

 

따라서 useNativeDriver 을 false 로 주니, 해결되었다.

 

useNativeDriver는 애니메이션 처리 작업을 자바스크립트 엔진이 아닌 네이티브 레벨에서 진행하게 하는 옵션으로 transform, opacity처럼 레이아웃과 관련없는 스타일에만 적용할 수 있다. 예를 들어 레이아웃에 영향을 끼치는 left, width, paddingLeft, marginLeft와 같은 스타일에는 꼭 useNativeDriver를 false로 지정해야 한다.

 

 

728x90
반응형
728x90
반응형

style 에 borderRadius 를 적용하면, 안드로이드에서는 radius가 적용되지만, ios에는 적용이 제대로 되지않는다.

 

<ImageBackground
     style={{
     width: deviceWidth - 2 * wrapperPadding,
     height: cardHeight,
     }}
     imageStyle={{ borderRadius: 15 }}
     source={require('../../assets/example/banner.png')}
></ImageBackground>

 

imageStyle props 에 넣어주면 된다.

728x90
반응형
728x90
반응형

피그마에 적혀있는 fontsize 대로 text style 에 입력하게되면, 기본 os의 글자크기에 영향을 많이 받는다.

 

const { fontScale } = Dimensions.get('window');
export const resizeFont = (fontSize: number) => {
return fontSize / fontScale;
};

로 함수를 만들고

 

해당하는 함수에 figma에 적혀있는 사이즈를 넣으면 os의 크기와 별개로 크기정렬이 잘된다.

728x90
반응형
728x90
반응형

Flatlist 나 scrollview에서 pagingEnabled 를 하면, snap 하는 느낌은 들지만, scroll의 offset 이 맞지않는 경우가 많다..

 

이를 위해

 

const offset = heightScale(600) + 2 * cardVerticalMargin;
const snapToOffsets = useMemo(
() =>
Array.from(Array(DUMMY.length)).map(
(_, index) => index * offset - headerHeight,
),
[DUMMY],
);
 

이런식으로 사용하면,

 

어느 offset들에게 걸릴지가 snapToOffesets props 에 의해 flatlist / scrollview 에 전달된다.

 

pagingEnabled
decelerationRate="fast"
scrollEventThrottle={16}
snapToOffsets={snapToOffsets}

주로 같이쓰는 옵션들이다.

728x90
반응형
728x90
반응형

 

이번 앱 디자인에는 animated 를 많이 사용한 UI 가 들어간다.

 

animated view 의 핵심은, state로 ui를 변화시키는 것을 지양하고, 

 

const animatedY = useRef(new Animated.Value(0)).current;

처럼 ref로 관리해줘야 리랜더링을 방지하여 매끄러운 애니메이션 효과가 나온다.

 

기본 사용은 

 

const translateY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, -bannerHeight + headerHeight + safeAreaTop],
extrapolate: 'clamp',
});

이런 형태로, translate를 한번해주어, 스크롤 input과 output 의 레인지를 계산해주고(해당 코드는 헤더가 스크롤할때 아래로 이동하도록 하는 코드이다)

 

clamp로 묶어주어, input range 의 밖의 값이 입력되었을때, output range의 큰 값이 출력되도록 해준다.

 

애니메이션 효과를 입히고 싶은 컴포넌트에 

 

<Animated.View
style={{
flexDirection: 'column',
transform: [{ translateY: animatedY }],
}}
>

해당 형태로 추가해준다.

 

animated.timing 으로 시작한 애니메이션도 있고, scroll을 통한 offset으로 해당하는 애니메이션도 있어 풀 소스코드를 올려놓는다.

 

const safeAreaTop = useSafeAreaInsets().top;

를 통해 ios safeArea에도 대처했다.

 

import { useCallback, useRef } from 'react';
import {
Animated,
Dimensions,
NativeScrollEvent,
NativeSyntheticEvent,
Platform,
Text,
UIManager,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { heightScale } from '../../util/Layout';
import { HomeHeader } from './HomeHeader';

const DUMMY = [
{
index: 0,
},
{
index: 1,
},
{
index: 2,
},
{
index: 3,
},
{
index: 4,
},
{
index: 5,
},
{
index: 6,
},
{
index: 7,
},
];

const deviceWidth = Dimensions.get('window').width;
const bannerHeight = (deviceWidth * 600) / 480;
const headerHeight = heightScale(75);

if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}

export const HomeScreen = () => {
let currentOffset = 0;
const animatedY = useRef(new Animated.Value(0)).current;
const animatedBackground = useRef(new Animated.Value(0)).current;
const animatedOpacity = useRef(new Animated.Value(0)).current;

const safeAreaTop = useSafeAreaInsets().top;

const animationStart = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
let direction =
event.nativeEvent.contentOffset.y > currentOffset ? 'down' : 'up';
currentOffset = event.nativeEvent.contentOffset.y;

if (
direction === 'down' &&
event.nativeEvent.contentOffset.y > bannerHeight / 3
) {
console.log('down');
Animated.parallel([
Animated.timing(animatedBackground, {
useNativeDriver: true,
duration: 300,
toValue: 1,
}),
Animated.timing(animatedOpacity, {
useNativeDriver: true,
duration: 300,
toValue: 1,
}),
]).start();
}
if (
direction === 'up' &&
event.nativeEvent.contentOffset.y < bannerHeight / 3
) {
console.log('up');
Animated.parallel([
Animated.timing(animatedBackground, {
useNativeDriver: true,
duration: 300,
toValue: 0,
}),
Animated.timing(animatedOpacity, {
useNativeDriver: true,
duration: 300,
toValue: 0,
}),
]).start();
}
},
[],
);

// const transY = Animated.
const translateY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, -bannerHeight + headerHeight + safeAreaTop],
extrapolate: 'clamp',
});

const translateHeaderY = animatedY.interpolate({
inputRange: [0, bannerHeight - headerHeight - safeAreaTop],
outputRange: [0, bannerHeight - headerHeight - safeAreaTop],
extrapolate: 'clamp',
});

const onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { y: animatedY } } }],
{
useNativeDriver: true,
listener: (event) => {
animationStart(event);
},
},
);

return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}
>
<Animated.FlatList
style={{ flex: 1 }}
data={DUMMY}
ListHeaderComponent={() => {
return (
<HomeHeader
animatedY={translateY}
translateHeaderY={translateHeaderY}
safeAreaTop={safeAreaTop}
animatedBackground={animatedBackground}
bannerHeight={bannerHeight}
animatedOpacity={animatedOpacity}
headerHeight={headerHeight}
/>
);
}}
onScroll={onScroll}
// pagingEnabled
stickyHeaderIndices={[0]}
renderItem={({ item, index }) => {
return (
<View
style={{
width: 100,
height: heightScale(600),
borderWidth: 1,
backgroundColor: 'pink',
}}
>
<Text style={{ fontSize: 40 }}>{index}</Text>
</View>
);
}}
/>
</View>
);
};
import React from 'react';
import {
Animated,
Dimensions,
Image,
TouchableOpacity,
View,
} from 'react-native';
import AlarmBlack from '../../assets/icons/alarmBlack.svg';
import AlarmWhite from '../../assets/icons/alarmWhite.svg';
import { heightScale, widthScale } from '../../util/Layout';

type HomeHeaderProps = {
animatedY: Animated.AnimatedInterpolation<string | number>;
translateHeaderY: Animated.AnimatedInterpolation<string | number>;
safeAreaTop: number;
animatedBackground: Animated.Value;
bannerHeight: number;
animatedOpacity: Animated.Value;
headerHeight: number;
};
const deviceWidth = Dimensions.get('window').width;

export const HomeHeader = ({
animatedY,
translateHeaderY,
safeAreaTop,
animatedBackground,
bannerHeight,
animatedOpacity,
headerHeight,
}: HomeHeaderProps) => {
return (
<Animated.View
style={{
flexDirection: 'column',
transform: [{ translateY: animatedY }],
}}
>
<Animated.View
style={{
position: 'absolute',
top: safeAreaTop,
width: deviceWidth,
height: headerHeight,
zIndex: 1,
transform: [{ translateY: translateHeaderY }],
backgroundColor: animatedBackground.interpolate({
inputRange: [0, 1],
outputRange: ['#FFFFFF00', '#FFFFFF'],
}),
alignItems: 'center',
justifyContent: 'center',
}}
>
<Image
source={require('../../assets/images/headerLogo.png')}
style={{
width: widthScale(50),
height: heightScale(35),
}}
resizeMode="contain"
/>
<TouchableOpacity
style={{
width: headerHeight,
height: headerHeight,
position: 'absolute',
right: 0,
justifyContent: 'center',
alignItems: 'center',
top: 0,
}}
>
<Animated.View
style={{
zIndex: 2,
position: 'absolute',
opacity: animatedOpacity,
}}
>
<AlarmBlack width={widthScale(40)} height={widthScale(40)} />
</Animated.View>
<AlarmWhite width={widthScale(40)} height={widthScale(40)} />
</TouchableOpacity>
</Animated.View>

<TouchableOpacity
style={{
width: deviceWidth,
height: bannerHeight,
backgroundColor: 'orange',
}}
></TouchableOpacity>
<View
style={{
width: '100%',
height: headerHeight,
backgroundColor: 'blue',
}}
></View>
<View
style={{
width: '100%',
height: headerHeight,
backgroundColor: 'red',
}}
></View>
</Animated.View>
);
};
728x90
반응형

+ Recent posts