Skip to content

Commit 9981672

Browse files
authored
onCanvasReady event listener (#93)
* feat: add `isCanvasReady()` validation before firing any native events * feat: init `onCanvasReady` event * chore: cleanup extra consoles on example app * docs: add `onCanvasReady` to README * fix: `topCanvasReady` error on iOS
1 parent fc06a07 commit 9981672

File tree

12 files changed

+197
-2
lines changed

12 files changed

+197
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ AppRegistry.registerComponent('example', () => Example);
9898
| permissionDialogTitle | `string` | Android Only: Provide a Dialog Title for the Image Saving PermissionDialog. Defaults to empty string if not set |
9999
| permissionDialogMessage | `string` | Android Only: Provide a Dialog Message for the Image Saving PermissionDialog. Defaults to empty string if not set |
100100
| onGenerateBase64 | `function` | An optional function which accepts 1 argument `result` containing the base64 string of the canvas. Called when `getBase64()` is invoked. |
101+
| onCanvasReady | `function` | An optional function called when the canvas is ready for interaction. |
101102

102103
#### Methods
103104

@@ -254,6 +255,7 @@ AppRegistry.registerComponent('example', () => Example);
254255
| strokeWidthStep | `number` | The step value of thickness when tapping `strokeWidthComponent`. |
255256
| savePreference | `function` | A function which is called when saving image and should return an object (see [below](#objects)). |
256257
| onSketchSaved | `function` | See [above](#properties) |
258+
| onCanvasReady | `function` | An optional function called when the canvas is ready for interaction. |
257259

258260
#### Methods
259261

android/src/main/java/com/sourcetoad/reactnativesketchcanvas/RNTSketchCanvasManager.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sourcetoad.reactnativesketchcanvas
22

33
import android.graphics.PointF
4+
import android.util.Log
45
import com.facebook.react.bridge.ReadableArray
56
import com.facebook.react.bridge.ReadableMap
67
import com.facebook.react.common.MapBuilder
@@ -36,7 +37,11 @@ class RNTSketchCanvasViewManager :
3637
commandId: String?,
3738
args: ReadableArray?
3839
) {
39-
mDelegate.receiveCommand(root, commandId, args)
40+
if (root.getSketchCanvas().isCanvasReady()) {
41+
mDelegate.receiveCommand(root, commandId, args)
42+
} else {
43+
Log.i("RNTSketchCanvas", "Canvas is not ready")
44+
}
4045
}
4146

4247
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
@@ -58,6 +63,11 @@ class RNTSketchCanvasViewManager :
5863
MapBuilder.of(
5964
"registrationName",
6065
"onGenerateBase64"
66+
),
67+
OnCanvasReadyEvent.EVENT_NAME,
68+
MapBuilder.of(
69+
"registrationName",
70+
"onCanvasReady"
6171
)
6272
)
6373
}

android/src/main/java/com/sourcetoad/reactnativesketchcanvas/SketchCanvas.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,17 @@ class SketchCanvas(context: ThemedReactContext) : View(context) {
4747
private val mArrCanvasText = ArrayList<CanvasText>()
4848
private val mArrTextOnSketch = ArrayList<CanvasText>()
4949
private val mArrSketchOnText = ArrayList<CanvasText>()
50+
private var mIsCanvasInitialized = false
5051

5152
fun getParentViewId(): Int {
5253
val parentView = parent as View
5354
return parentView.id
5455
}
5556

57+
fun isCanvasReady(): Boolean {
58+
return mIsCanvasInitialized && mDrawingCanvas != null
59+
}
60+
5661
private fun getFileUri(filepath: String): Uri {
5762
var uri = Uri.parse(filepath)
5863
if (uri.scheme == null) {
@@ -420,6 +425,17 @@ class SketchCanvas(context: ThemedReactContext) : View(context) {
420425
}
421426
mNeedsFullRedraw = true
422427
invalidate()
428+
429+
if (!mIsCanvasInitialized) {
430+
UIManagerHelper.getEventDispatcherForReactTag(mContext, getParentViewId())
431+
?.dispatchEvent(
432+
OnCanvasReadyEvent(
433+
UIManagerHelper.getSurfaceId(mContext),
434+
getParentViewId()
435+
)
436+
)
437+
mIsCanvasInitialized = true
438+
}
423439
}
424440
}
425441

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.sourcetoad.reactnativesketchcanvas
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
7+
class OnCanvasReadyEvent(
8+
surfaceId: Int,
9+
viewId: Int,
10+
) : Event<OnCanvasReadyEvent>(surfaceId, viewId) {
11+
override fun getEventName(): String {
12+
return EVENT_NAME
13+
}
14+
15+
override fun getEventData(): WritableMap {
16+
val eventData = Arguments.createMap()
17+
return eventData
18+
}
19+
20+
companion object {
21+
const val EVENT_NAME = "onCanvasReady"
22+
}
23+
}

example/src/App.tsx

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,102 @@ type ExampleState = {
2626
scrollEnabled: boolean;
2727
};
2828

29+
const testPath1 = [
30+
{
31+
path: {
32+
id: 48034205,
33+
color: '#FF0000',
34+
width: 5,
35+
data: [
36+
'565.47,259.47',
37+
'565.47,254',
38+
'564.45,238.63',
39+
'559.84,216.66',
40+
'552.9,191.47',
41+
'541.21,161.56',
42+
'523.7,133.11',
43+
'508.1,114.47',
44+
'497.58,105.88',
45+
'482.46,98.99',
46+
'467.64,97.48',
47+
'459.36,100.76',
48+
'454.79,107.44',
49+
'447.61,134.91',
50+
'444.44,166.78',
51+
'442.97,203.28',
52+
'442.97,220.49',
53+
'456.83,260.94',
54+
'479.03,291.37',
55+
'503.71,316.47',
56+
'535.02,345.02',
57+
'585.24,388.53',
58+
'606.96,406.27',
59+
'624.82,419.17',
60+
'633.8,427.67',
61+
'634.96,428.95',
62+
'634.96,429.48',
63+
'630.39,420.75',
64+
'628.48,418.48',
65+
],
66+
},
67+
size: { width: 1280, height: 652 },
68+
drawer: null,
69+
},
70+
{
71+
path: {
72+
id: 50223646,
73+
color: '#FF0000',
74+
width: 5,
75+
data: [
76+
'567.97,256.49',
77+
'565.82,252.94',
78+
'566.45,252.48',
79+
'565.47,247.71',
80+
'563.95,238.16',
81+
'563.95,224.89',
82+
'563.95,214.86',
83+
'568.62,201.67',
84+
'578.46,182.59',
85+
'592.39,163.97',
86+
'609.14,148.3',
87+
'625.33,139.49',
88+
'647.67,131.57',
89+
'663.59,127.37',
90+
'682.97,123.97',
91+
'703.48,123.97',
92+
'721.17,123.97',
93+
'735.32,123.97',
94+
'746.95,134.1',
95+
'753.79,145.92',
96+
'756.45,161.81',
97+
'756.45,184.47',
98+
'754.51,209.8',
99+
'747.82,230.05',
100+
'736.97,251.61',
101+
'721.58,275.1',
102+
'700.22,300.01',
103+
'685.44,318.33',
104+
'673.64,333.23',
105+
'661.29,349.32',
106+
'655.36,358.3',
107+
'652.93,364.94',
108+
'649.45,371.52',
109+
'647.35,385.27',
110+
'645.57,397.08',
111+
'641.91,410.31',
112+
'640.93,418.06',
113+
'639.97,421.95',
114+
'637.97,425.87',
115+
'637.97,427.81',
116+
'637.97,428.7',
117+
'637.97,428.97',
118+
],
119+
},
120+
size: { width: 1280, height: 652 },
121+
drawer: null,
122+
},
123+
];
124+
29125
export default class example extends Component<any, ExampleState> {
30126
constructor(props: any) {
31127
super(props);
@@ -86,6 +182,13 @@ export default class example extends Component<any, ExampleState> {
86182
<TouchableOpacity
87183
onPress={() => {
88184
this.setState({ example: 2 });
185+
186+
if (this.canvas) {
187+
// debug: attempt to load paths immediately without waiting for onCanvasReady
188+
testPath1.forEach((path) => {
189+
this.canvas.addPath(path);
190+
});
191+
}
89192
}}
90193
>
91194
<Text
@@ -163,6 +266,9 @@ export default class example extends Component<any, ExampleState> {
163266
<RNSketchCanvas
164267
containerStyle={{ backgroundColor: 'transparent', flex: 1 }}
165268
canvasStyle={{ backgroundColor: 'transparent', flex: 1 }}
269+
onCanvasReady={() => {
270+
console.log('onCanvasReady');
271+
}}
166272
onStrokeEnd={(data) => {}}
167273
closeComponent={
168274
<View style={styles.functionButton}>
@@ -239,6 +345,7 @@ export default class example extends Component<any, ExampleState> {
239345
};
240346
}}
241347
onSketchSaved={(success, path) => {
348+
console.log('onSketchSaved', success, path);
242349
Alert.alert(
243350
success ? 'Image saved!' : 'Failed to save image!',
244351
path
@@ -293,6 +400,14 @@ export default class example extends Component<any, ExampleState> {
293400
directory: SketchCanvas.MAIN_BUNDLE,
294401
mode: 'AspectFit',
295402
}}
403+
onCanvasReady={() => {
404+
console.log('onCanvasReady #2');
405+
406+
// debug path #2
407+
testPath1.forEach((path) => {
408+
this.canvas.addPath(path);
409+
});
410+
}}
296411
// localSourceImage={{ filename: 'bulb.png', directory: RNSketchCanvas.MAIN_BUNDLE }}
297412
ref={(ref) => (this.canvas = ref)}
298413
style={{ flex: 1 }}
@@ -353,7 +468,6 @@ export default class example extends Component<any, ExampleState> {
353468
{ backgroundColor: 'black', width: 90 },
354469
]}
355470
onPress={() => {
356-
console.log(this.canvas.getPaths());
357471
Alert.alert(JSON.stringify(this.canvas.getPaths()));
358472
this.canvas.getBase64('jpg', false, true, true, true);
359473
}}

ios/RNSketchCanvasViewComponent.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ -(void)prepareForRecycle {
6767
[self setupView];
6868
}
6969

70+
7071
_isInitialValueSet = NO;
7172
}
7273

@@ -161,6 +162,10 @@ - (void)handleEvent:(NSDictionary *)eventData {
161162
};
162163

163164
self.eventEmitter.onChange(result);
165+
} else if ([eventData[@"eventType"] isEqualToString:@"onCanvasReady"]) {
166+
RNTSketchCanvasEventEmitter::OnCanvasReady result{};
167+
168+
self.eventEmitter.onCanvasReady(result);
164169
} else {
165170
RNTSketchCanvasEventEmitter::OnChange result{
166171
.eventType = std::string("save"),

ios/RNSketchCanvasViewManager.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ - (UIView *)view
1818
RCT_EXPORT_VIEW_PROPERTY(text, NSArray)
1919
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
2020
RCT_EXPORT_VIEW_PROPERTY(onGenerateBase64, RCTDirectEventBlock)
21+
RCT_EXPORT_VIEW_PROPERTY(onCanvasReady, RCTDirectEventBlock)
2122

2223
@end

ios/SketchCanvas/RNSketchCanvas.m

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ @implementation RNSketchCanvas
1515
CGContextRef _drawingContext, _translucentDrawingContext;
1616
CGImageRef _frozenImage, _translucentFrozenImage;
1717
BOOL _needsFullRedraw;
18+
BOOL _canvasIsReady;
1819

1920
UIImage *_backgroundImage;
2021
UIImage *_backgroundImageScaled;
@@ -27,6 +28,7 @@ - (instancetype)initWithFrame:(CGRect)frame {
2728
if (self = [super initWithFrame:frame]) {
2829
_paths = [NSMutableArray new];
2930
_needsFullRedraw = YES;
31+
_canvasIsReady = NO;
3032

3133
self.backgroundColor = [UIColor clearColor];
3234
self.clearsContextBeforeDrawing = YES;
@@ -62,6 +64,7 @@ - (void)invalidate {
6264
_paths = nil;
6365
_currentPath = nil;
6466
_lastSize = CGSizeZero;
67+
_canvasIsReady = NO;
6568
_needsFullRedraw = YES;
6669
}
6770

@@ -187,10 +190,12 @@ - (void)createDrawingContext {
187190
if (_drawingContext) {
188191
CGContextRelease(_drawingContext);
189192
_drawingContext = nil;
193+
_canvasIsReady = NO;
190194
}
191195
if (_translucentDrawingContext) {
192196
CGContextRelease(_translucentDrawingContext);
193197
_translucentDrawingContext = nil;
198+
_canvasIsReady = NO;
194199
}
195200

196201
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
@@ -212,9 +217,17 @@ - (void)createDrawingContext {
212217
CGContextRelease(_translucentDrawingContext);
213218
_translucentDrawingContext = nil;
214219
}
220+
_canvasIsReady = NO;
215221
} else {
216222
CGContextConcatCTM(_drawingContext, CGAffineTransformMakeScale(scale, scale));
217223
CGContextConcatCTM(_translucentDrawingContext, CGAffineTransformMakeScale(scale, scale));
224+
225+
if (!_canvasIsReady) {
226+
if ([self.eventDelegate respondsToSelector:@selector(handleEvent:)]) {
227+
[self.eventDelegate handleEvent: @{ @"eventType": @"onCanvasReady" }];
228+
}
229+
_canvasIsReady = YES;
230+
}
218231
}
219232

220233
CGColorSpaceRelease(colorSpace);

src/SketchCanvas.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ class SketchCanvas extends React.Component<SketchCanvasProps, CanvasState> {
355355
onGenerateBase64={(e: any) => {
356356
this.props.onGenerateBase64?.(e.nativeEvent || {});
357357
}}
358+
onCanvasReady={() => {
359+
this.props.onCanvasReady?.();
360+
}}
358361
localSourceImage={this.props.localSourceImage}
359362
permissionDialogTitle={this.props.permissionDialogTitle}
360363
permissionDialogMessage={this.props.permissionDialogMessage}

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export default class RNSketchCanvas extends React.Component<
320320
this.props.onSketchSaved?.(success, path)
321321
}
322322
onPathsChange={this.props.onPathsChange}
323+
onCanvasReady={this.props.onCanvasReady}
323324
text={this.props.text}
324325
localSourceImage={this.props.localSourceImage}
325326
permissionDialogTitle={this.props.permissionDialogTitle}

0 commit comments

Comments
 (0)