Save Sketched lines in LocalStorage - react-native

Sorry im posting this here
I want to save the info of each line i drew on canvas(the save action would be called onChange)
so i can retrieve this data and draw it on canvas again in case the user change screen or something.
I'm using expo-pixi to draw a image and sketch over it
onChangeAsync = async (param) => {
// here i want on change code i get the line informantion and store it
}
onLayout = async ({
nativeEvent: {
layout: { width, height },
},
}) => {
this.setState({
layoutWidth: width,
layoutHeight: height,
})
this.onReady();
}
onReady = async () => {
const { layoutWidth, layoutHeight, points } = this.state;
this.sketch.graphics = new PIXI.Graphics();
if (this.sketch.stage) {
if (layoutWidth && layoutHeight) {
const background = await PIXI.Sprite.fromExpoAsync(this.props.image);
background.width = layoutWidth * scaleR;
background.height = layoutHeight * scaleR;
this.sketch.stage.addChild(background);
this.sketch.renderer._update();
}
}
};
// The sketch component is pretty much as the example which comes with the lib
<Sketch
ref={ref => (this.sketch = ref)}
style={styles.sketch}
strokeColor={this.state.strokeColor}
strokeWidth={this.state.strokeWidth}
strokeAlpha={1}
onChange={this.onChangeAsync}
onReady={this.onReady}
/>
Does anyone have any clue? im kind of desperate
Thanks

Related

Translate lil-gui controls using i18n

Im working on a threejs scene which gives user the ability to dynamically adjust lighting i.e color and intensity and save the settings to the DB. I am facing an issue while translating the color and intensity labels using i18n. Here is how i am adding control folders on button click
customFunctionFolder3
.add(topFrontControllers, 'topFrontCheckbox')
.name(this.i18n['TopFrontLight'])
.onChange((val: any) => {
if (val === false) {
topFrontLightFolder.destroy();
this.topFrontChecked = false;
} else {
topFrontLightFolder = customFunctionFolder3.addFolder(this.i18n['AdjustFrontLight']);
topFrontLightFolder.addColor(topFrontLightParams, 'color').onChange((val: any) => {
this.topFrontLight.color.setHex(val);
});
topFrontLightFolder
.add(topFrontLightParams, 'intensity', 0, 10)
.onChange((val: any) => {
this.topFrontLight.intensity = val;
});
topFrontLightFolder.open();
}
});
topFrontLightFolder = customFunctionFolder3.addFolder(this.i18n['AdjustFrontLight']);
topFrontLightFolder.addColor(topFrontLightParams, 'color').onChange((val: any) => {
this.topFrontLight.color.setHex(val);
});
topFrontLightFolder.add(topFrontLightParams, 'intensity', 0, 10).onChange((val: any) => {
this.topFrontLight.intensity = val;
});
topFrontLightFolder.open();
I want to translate the color and intensity labels too but I am not able to do so.

React native reanimated runOnJs - does not update state every time

I have a list of items that should change state when they are swiped passed a certain threshold. I'm using runOnJs to call a function to change the state. Now when I swipe an item the first time, it updates it's state but every swipe after that does nothing. Can someone please explain to me what I'm missing here?
let [cleaned, setCleaned] = useState(false);
let handleCleanPress = () => {
console.log(clean);
setCleaned(!cleaned);
translateX.value = withTiming(0);
};
let panGesture = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onStart: (_, context) => {
context.startX = translateX.value;
},
onActive: (event, context) => {
let start = context.startX + event.translationX;
if (start < 0) {
translateX.value = start;
}
},
onEnd: () => {
let shouldTriggerClean = translateX.value < translateXThreshold;
translateX.value =
translateX.value >= snapThreshold && translateX.value < -BUTTON_WIDTH
? withTiming(snapPoint, { duration: 200 })
: withTiming(0, { duration: 200 });
if (shouldTriggerClean) {
runOnJS(handleCleanPress)();
}
},
});
Feels a bit wrong doing it like this but it works. Maybe someone can suggest a better way or confirm this is correct?
let setCleanState = () => {
setCleaned(!cleaned);
};
let handleCleanPress = () => {
translateX.value = withTiming(0, { duration: 200 }, (finished) => {
if (finished) {
runOnJS(setCleanState)();
}
});
};
I think part of the problem here may be that you're mixing the "JS in UI Thread"("worklets", translateX.value) with the "Main React Native JS Thread"(setState).
Read more about that [here][1].
You fixed that in your follow-up comment by only using runOnJS on setCleanState. Which I think is why it was working, albeit not reliably.
Did you also remove the withTiming functions in your onEnd() after your comment?
[1]: https://docs.swmansion.com/react-native-reanimated/docs/#:~:text=interactions%20and%20animations,the%20UI%20thread).

How to use diffClamp in reanimated 2?

I am trying to hide and show the header based on the scroll event from reanimated 2 useAnimatedScrollHandler and I need to use the diffClamp so whenever the user scrolls up the header should be shown in less time than the whole scroll event contentOffset.y value but the problem is diffClamp is I think from reanimated v1 and I need to use useAnimatedStyle hook in order to animate styles in reanimated v2 and finally it gives an error.
Can someone please help with it?
const clamp = (value, lowerBound, upperBound) => {
"worklet";
return Math.min(Math.max(lowerBound, value), upperBound);
};
const scrollClamp = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event, ctx) => {
const diff = event.contentOffset.y - ctx.prevY;
scrollClamp.value = clamp(scrollClamp.value + diff, 0, 200);
},
onBeginDrag: (event, ctx) => {
ctx.prevY = event.contentOffset.y;
}
});
const RStyle = useAnimatedStyle(() => {
const interpolateY = interpolate(
scrollClamp.value,
[0, 200],
[0, -200],
Extrapolate.CLAMP
)
return {
transform: [
{ translateY: interpolateY }
]
}
})

React-Native Flatlist's onViewableItemChanged error

I am trying to account for changes when the element in view changes.
I get the following
Invariant Violation: Changing onViewableItemsChanged on the fly is not supported
I have the following
<Animated.FlatList
viewabilityConfig={carouselViewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
...//other properties
/>
and my functions are
const onViewableItemsChanged = ({
viewableItems,
}: {
viewableItems: Array<number>;
}) => {
const insightsById = savedInsights.byId;
console.log('byId:!', insightsById);
if (!viewableItems.length) {
return;
}
const visibleInsightId =
insightsById[viewableItems[Math.max(viewableItems.length - 2, 0)].index];
console.log(visibleInsightId);
Analytics.logViewItem({ insight_id: visibleInsightId });
// const visibleInsightIndex = insightsById.indexOf(visibleInsightId);
};
const carouselViewabilityConfig = {
waitForInteraction: false,
minimumViewTime: 100,
viewAreaCoveragePercentThreshold: 50,
};
I saw a method using refs but it didn't work, my function wouldn't run.
Any help would be appreciated. Thanks.

React Native ListView - rowHasChanged doesn't fire

I am trying to implement an infinite scroll in React Native. Below is the source of the component:
var React = require('react-native');
var server = require('../server');
var Post = require('./Post');
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout');
var backEvent = null;
var lastPostId = "";
var isLoadingMore = false;
var isLoadingTop = false;
var onEndReachedActive = false;
var {
StyleSheet,
ListView,
View,
Text,
Image,
ProgressBarAndroid,
BackAndroid
} = React;
class Stream extends React.Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => {
console.log("rowHasChenged FIRED!!");
return false;
}
});
this.state = {
dataSource: this.ds.cloneWithRows(['loader']),
hasStream: false,
posts: []
};
}
componentDidMount() {
BackAndroid.addEventListener('hardwareBackPress', () => {
this.props.navigator.jumpBack();
return true;
}.bind(this));
server.getStream('', '', 15).then((res) => {
lastPostId = res[res.length-1].m._id;
this.setState({
posts: res,
hasStream: true,
dataSource: this.ds.cloneWithRows(res)
}, () => onEndReachedActive = true);
})
}
onRefresh() {
var posts = this.state.posts;
var firstPost = posts[0].m._id;
console.log(this.state.dataSource._rowHasChanged);
isLoadingTop = true;
server.getStream('', firstPost, 4000)
.then(res => {
console.log(posts.length);
posts = res.concat(posts);
console.log(posts.length);
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, () => {
this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
isLoadingTop = false;
});
}).catch((err) => {
isLoadingTop = false;
})
}
onEndReached(event) {
if(!onEndReachedActive) return;
if(this.state.loadingMore || this.state.isLoadingTop)return;
isLoadingMore = true;
var posts = this.state.posts;
server.getStream(posts[posts.length-1].m._id, '', 15)
.then(res => {
console.log('received posts');
posts = posts.concat(res);
lastPostId = posts[posts.length-1].m._id;
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, ()=>isLoadingMore = false);
})
}
renderHeader() {
return (
<View style={styles.header}>
<Text style={styles.headerText}>Header</Text>
</View>
)
}
renderRow(post) {
if(post === 'loader') {
return (
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/>
)
}
let hasLoader = post.m._id === lastPostId;
let loader = hasLoader ?
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/> : null;
return (
<View>
<Post
post={post}/>
{loader}
</View>
)
}
render() {
return (
<ListView
style={styles.mainContainer}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={1}
pageSize={15} />
);
}
}
The problem is that whenever I append (or prepend) new data, the rowHasChanged method of the DataSource doesn't fire. It just re-renders every row, even tho nothing has changed (except the new data).
Any idea why the method is bypassed?
Edit: Pass a function to setState to avoid race conditions
I just figured it out. If you are having the same issue, check the point at which you change your state with the new dataSource. Mine was like this:
this.setState({
dataSource: this.ds.cloneWithRows(posts)
});
Instead you should always use the dataSource from the previous state, like this:
this.setState(state => ({
dataSource: state.dataSource.cloneWithRows(posts)
}))
Cheers!
this worked for me, hope this helps. I created a new dataSource and assigned the updated data to it on state change as follows:`
var dataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => ( r1 !== r2)});
this.setState({ dataSource : dataSource.cloneWithRows(posts) });
Now, the new data is assigned and the view is rendered correctly. Note that posts array that is assigned now holds the updated data. Still wondering though if it's the best way to do it but it works!
I agree it seems to make sense that you should always use the dataSource from the previous state.
Yet when I setState this way, rowHasChanged gets called for all rows, however, rowHasChanged always returns false and no rows are rendered??? Why?
// This is callback handler that the ListView DetailView will
// call when a ListView item is edited
onChange(waypoint: Object){
console.log('Callback: rowNumber= ', waypoint.rowNumber);
console.log(' length(m)= ', waypoint.distance.meters);
var itemListChanged = this.state.itemList;
itemListChanged[waypoint.rowNumber-1] = waypoint;
this.setState({
dataSource: this.state.dataSource.cloneWithRows(itemListChanged),
});
},
If I setState this way, renderRow is called for all rows unconditionally without rowHasChanged ever being called. Which is correct?
this.setState({
dataSource: ds.cloneWithRows(itemListChanged),
});
ListView, datasource, and react-native are a hard learning curve coming from C#/C/C++.
for anyone still having issue with rowHasChanged called but are still returning false the following snippets might help
the datasource is initialized like usual:
let ds = new ListView.DataSource ({
rowHasChanged: (a, b) => {
const changed = (a !== b)
return changed
}
})
this.data = []
this.state = {
listDataSource: ds.cloneWithRows(this.data)
}
here is the function which will update a row
updateRow = (row, rowId, sectionId) => {
// make a shallow clone from the stored data array
let blob = this.data.concat()
// modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref)
blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'})
// tell react to update the source
this.setState({
listDataSource: this.state.listDataSource.cloneWithRows(blob)
}, () => {
// we need to update our data storage here! after the state is changed
this.data = blob
})
}